Repository: baobabsoluciones/cornflow (language: python)

Issue:
Control dag-level permissions in cornflow / airflow
**Is your feature request related to a problem? Please describe.**
Sometimes I'm a user who should not have access to certain solvers.

**Describe the solution you'd like**
I would like to be able to assign dag permissions to roles so only certain users would have access to certain dags/solvers.

**Describe alternatives you've considered**
Maybe we could store that in airflow and then cornflow would then use the cornflow user to get the dag information from airflow?
https://airflow.apache.org/docs/apache-airflow/stable/security/access-control.html#dag-level-role




Discussion / hints:
Couldn't we implement this with user group? And the group the user belongs
to, indicates the dags the user have access to, that way they can not even
create an instance pointing to that schema.

On Wed, Jul 28, 2021, 19:39 Franco Peschiera ***@***.***>
wrote:

> *Is your feature request related to a problem? Please describe.*
> Sometimes I'm a user who should not have access to certain solvers.
>
> *Describe the solution you'd like*
> I would like to be able to assign dag permissions to roles so only certain
> users would have access to certain dags/solvers.
>
> *Describe alternatives you've considered*
> Maybe we could store that in airflow and then cornflow would then use the
> cornflow user to get the dag information from airflow?
>
> https://airflow.apache.org/docs/apache-airflow/stable/security/access-control.html#dag-level-role
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/baobabsoluciones/corn/issues/80>, or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AF4DGHGN6ZS7O2A4PADT7RDT2A6GHANCNFSM5BE2G5KA>
> .
>

It's possible and it's true we have talked about it.

Currently, cornflow does not have a list of avaliable dags. If we decide to
keep the dags only in airflow, then maybe it makes sense to control access
there. That way cornflow asks airflow (as it does now) for information on
dags. This implies we would need to replicate cornflows roles in airflow.

If we decide to create a dag catalog in cornflow that is synced with
airflow, then we could give permission at the dag level.
This implies that we would have to replicate airflow dag information in
cornflow.

There are probably other factors that I'm not thinking of that are
relevant.

On Wed, Jul 28, 2021, 20:03 Guillermo González-Santander de la Cruz <
***@***.***> wrote:

> Couldn't we implement this with user group? And the group the user belongs
> to, indicates the dags the user have access to, that way they can not even
> create an instance pointing to that schema.
>
> On Wed, Jul 28, 2021, 19:39 Franco Peschiera ***@***.***>
> wrote:
>
> > *Is your feature request related to a problem? Please describe.*
> > Sometimes I'm a user who should not have access to certain solvers.
> >
> > *Describe the solution you'd like*
> > I would like to be able to assign dag permissions to roles so only
> certain
> > users would have access to certain dags/solvers.
> >
> > *Describe alternatives you've considered*
> > Maybe we could store that in airflow and then cornflow would then use the
> > cornflow user to get the dag information from airflow?
> >
> >
> https://airflow.apache.org/docs/apache-airflow/stable/security/access-control.html#dag-level-role
> >
> > —
> > You are receiving this because you are subscribed to this thread.
> > Reply to this email directly, view it on GitHub
> > <https://github.com/baobabsoluciones/corn/issues/80>, or unsubscribe
> > <
> https://github.com/notifications/unsubscribe-auth/AF4DGHGN6ZS7O2A4PADT7RDT2A6GHANCNFSM5BE2G5KA
> >
> > .
> >
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/baobabsoluciones/corn/issues/80#issuecomment-888510814>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABJUZ44DVJKHVOSA7ZP2VBDT2BA5PANCNFSM5BE2G5KA>
> .
>


Fix patch:
diff --git a/airflow_config/dags/model_functions.py b/airflow_config/dags/model_functions.py
index 5e4b7d4d..7895435e 100644
--- a/airflow_config/dags/model_functions.py
+++ b/airflow_config/dags/model_functions.py
@@ -1,4 +1,3 @@
-
 import cornflow_client.airflow.dag_utilities as utils
 from cornflow_client import get_pulp_jsonschema
 import pulp as pl
@@ -6,7 +5,7 @@
 import os
 
 
-name = 'solve_model_dag'
+name = "solve_model_dag"
 instance = solution = get_pulp_jsonschema()
 config = get_pulp_jsonschema("solver_config.json")
 
@@ -21,10 +20,10 @@ def solve(data, config):
     var, model = pl.LpProblem.from_dict(data)
 
     # we overwrite the logPath argument before solving.
-    log_path = config['logPath'] = 'temp.log'
-    config['msg'] = 0
-    if 'solver' not in config:
-        config['solver'] = 'PULP_CBC_CMD'
+    log_path = config["logPath"] = "temp.log"
+    config["msg"] = 0
+    if "solver" not in config:
+        config["solver"] = "PULP_CBC_CMD"
     try:
         solver = pl.getSolverFromDict(config)
     except pl.PulpSolverError:
@@ -39,21 +38,28 @@ def solve(data, config):
         log = f.read()
 
     # we convert the log into orloge json
-    equivs = \
-        dict(
-            CPLEX_CMD='CPLEX', CPLEX_PY='CPLEX', CPLEX_DLL='CPLEX',
-            GUROBI='GUROBI', GUROBI_CMD='GUROBI',
-            PULP_CBC_CMD='CBC', COIN_CMD='CBC'
-        )
+    equivs = dict(
+        CPLEX_CMD="CPLEX",
+        CPLEX_PY="CPLEX",
+        CPLEX_DLL="CPLEX",
+        GUROBI="GUROBI",
+        GUROBI_CMD="GUROBI",
+        PULP_CBC_CMD="CBC",
+        COIN_CMD="CBC",
+    )
     solver_name = equivs.get(solver.name)
     log_dict = None
     if solver_name:
         try:
-            log_dict = ol.get_info_solver(path=log, solver=solver_name, get_progress=True, content=True)
+            log_dict = ol.get_info_solver(
+                path=log, solver=solver_name, get_progress=True, content=True
+            )
         except:
             log_dict = dict()
         else:
-            log_dict['progress'] = log_dict['progress'].fillna('').to_dict(orient='list')
+            log_dict["progress"] = (
+                log_dict["progress"].fillna("").to_dict(orient="list")
+            )
     print("Log read")
 
     try:
diff --git a/airflow_config/dags/optim_dag.py b/airflow_config/dags/optim_dag.py
index 46065037..96095d61 100644
--- a/airflow_config/dags/optim_dag.py
+++ b/airflow_config/dags/optim_dag.py
@@ -3,6 +3,7 @@
 from airflow.secrets.environment_variables import EnvironmentVariablesBackend
 import model_functions as mf
 
+# TODO: review if needed
 dag = DAG(mf.name, default_args=mf.utils.default_args, schedule_interval=None)
 
 
diff --git a/cornflow/app.py b/cornflow/app.py
index 4fc97036..4229ddb1 100644
--- a/cornflow/app.py
+++ b/cornflow/app.py
@@ -10,6 +10,7 @@
 
 from .commands.access import access_init_command
 from .commands.actions import register_actions_command
+from .commands.dag import register_deployed_dags_command
 from .commands.permissions import register_base_permissions_command
 from .commands.roles import register_roles_command
 from .commands.users import create_admin_user_command, create_service_user_command
@@ -29,6 +30,7 @@ def create_app(env_name="development", dataconn=None):
     :return: the application that is going to be running :class:`Flask`
     :rtype: :class:`Flask`
     """
+
     app = Flask(__name__)
     app.config.from_object(app_config[env_name])
     # initialization for init_cornflow_service.py
@@ -67,6 +69,7 @@ def _fk_pragma_on_connect(dbapi_con, con_record):
     app.cli.add_command(register_views)
     app.cli.add_command(register_base_assignations)
     app.cli.add_command(access_init)
+    app.cli.add_command(register_deployed_dags)
 
     return app
 
@@ -126,6 +129,16 @@ def access_init(verbose):
     access_init_command(verbose)
 
 
+@click.command("register_deployed_dags")
+@click.option("-r", "--url", type=str)
+@click.option("-u", "--username", type=str)
+@click.option("-p", "--password", type=str)
+@click.option("-v", "--verbose", default=0)
+@with_appcontext
+def register_deployed_dags(url, username, password, verbose):
+    register_deployed_dags_command(url, username, password, verbose)
+
+
 if __name__ == "__main__":
     environment_name = os.getenv("FLASK_ENV", "development")
     # env_name = 'development'
diff --git a/cornflow/commands/actions.py b/cornflow/commands/actions.py
index 9aed47f6..460f466f 100644
--- a/cornflow/commands/actions.py
+++ b/cornflow/commands/actions.py
@@ -1,4 +1,6 @@
 def register_actions_command(verbose):
+    from sqlalchemy.exc import IntegrityError
+
     from ..models import ActionModel
     from ..shared.const import ACTIONS_MAP
     from ..shared.utils import db
@@ -16,7 +18,10 @@ def register_actions_command(verbose):
     if len(actions_to_register) > 0:
         db.session.bulk_save_objects(actions_to_register)
 
-    db.session.commit()
+    try:
+        db.session.commit()
+    except IntegrityError:
+        db.session.rollback()
 
     if "postgres" in str(db.session.get_bind()):
         db.engine.execute(
diff --git a/cornflow/commands/dag.py b/cornflow/commands/dag.py
new file mode 100644
index 00000000..2a3a89df
--- /dev/null
+++ b/cornflow/commands/dag.py
@@ -0,0 +1,67 @@
+# TODO: add command to register if there is new DAGs.
+#  This should be executed on each deployment.
+def register_deployed_dags_command(
+    url: str = None, user: str = None, pwd: str = None, verbose: int = 0
+):
+    import time
+
+    #
+    from cornflow_client.airflow.api import Airflow
+
+    from sqlalchemy.exc import IntegrityError
+    from ..models import DeployedDAG
+    from ..shared.utils import db
+
+    af_client = Airflow(url, user, pwd)
+    max_attempts = 20
+    attempts = 0
+    while not af_client.is_alive() and attempts < max_attempts:
+        attempts += 1
+        if verbose == 1:
+            print(f"Airflow is not reachable (attempt {attempts})")
+        time.sleep(15)
+
+    if not af_client.is_alive():
+        if verbose == 1:
+            print("Airflow is not reachable")
+        return False
+
+    dags_registered = [dag.id for dag in DeployedDAG.get_all_objects()]
+
+    response = af_client.get_model_dags()
+    dag_list = response.json()["dags"]
+
+    processed_dags = [
+        DeployedDAG({"id": dag["dag_id"], "description": dag["description"]})
+        for dag in dag_list
+        if dag["dag_id"] not in dags_registered
+    ]
+
+    if len(processed_dags) > 0:
+        db.session.bulk_save_objects(processed_dags)
+
+    try:
+        db.session.commit()
+    except IntegrityError as e:
+        db.session.rollback()
+
+    if verbose == 1:
+        if len(processed_dags) > 0:
+            print(f"DAGs registered: {processed_dags}")
+        else:
+            print("No new DAGs")
+    return True
+
+
+def register_deployed_dags_command_test(dags: list = None, verbose=0):
+    from ..models import DeployedDAG
+
+    if dags is None:
+        dags = ["solve_model_dag", "gc", "timer"]
+
+    deployed_dag = [DeployedDAG({"id": dag, "description": None}) for dag in dags]
+    for dag in deployed_dag:
+        dag.save()
+
+    if verbose == 1:
+        print("Registered DAGs")
diff --git a/cornflow/commands/permissions.py b/cornflow/commands/permissions.py
index 21665701..53a9fce6 100644
--- a/cornflow/commands/permissions.py
+++ b/cornflow/commands/permissions.py
@@ -1,4 +1,6 @@
 def register_base_permissions_command(verbose):
+    from sqlalchemy.exc import IntegrityError
+
     from ..endpoints import resources
     from ..models import ApiViewModel, PermissionViewRoleModel
     from ..shared.const import BASE_PERMISSION_ASSIGNATION, EXTRA_PERMISSION_ASSIGNATION
@@ -50,7 +52,10 @@ def register_base_permissions_command(verbose):
     if len(permissions_to_register) > 0:
         db.session.bulk_save_objects(permissions_to_register)
 
-    db.session.commit()
+    try:
+        db.session.commit()
+    except IntegrityError:
+        db.session.commit()
 
     if "postgres" in str(db.session.get_bind()):
         db.engine.execute(
@@ -65,3 +70,52 @@ def register_base_permissions_command(verbose):
             print("No new permissions to register")
 
     return True
+
+
+def register_dag_permissions_command(open_deployment: int = 1, verbose: int = 0):
+
+    from sqlalchemy.exc import IntegrityError
+
+    from ..models import DeployedDAG, PermissionsDAG, UserModel
+    from ..shared.utils import db
+
+    existing_permissions = [
+        (permission.dag_id, permission.user_id)
+        for permission in PermissionsDAG.get_all_objects()
+    ]
+
+    db.session.commit()
+    all_users = UserModel.get_all_users()
+    all_dags = DeployedDAG.get_all_objects()
+
+    if open_deployment == 1:
+        permissions = [
+            PermissionsDAG({"dag_id": dag.id, "user_id": user.id})
+            for user in all_users
+            for dag in all_dags
+            if (dag.id, user.id) not in existing_permissions
+        ]
+
+    else:
+        permissions = [
+            PermissionsDAG({"dag_id": dag.id, "user_id": user.id})
+            for user in all_users
+            for dag in all_dags
+            if (dag.id, user.id) not in existing_permissions and user.is_service_user()
+        ]
+
+    if len(permissions) > 1:
+        db.session.bulk_save_objects(permissions)
+
+    try:
+        db.session.commit()
+    except IntegrityError:
+        db.session.rollback()
+
+    if verbose == 1:
+        if len(permissions) > 1:
+            print(f"DAG permissions registered: {permissions}")
+        else:
+            print("No new DAG permissions")
+
+    pass
diff --git a/cornflow/commands/roles.py b/cornflow/commands/roles.py
index c4ac6f55..0da6c88a 100644
--- a/cornflow/commands/roles.py
+++ b/cornflow/commands/roles.py
@@ -1,4 +1,6 @@
 def register_roles_command(verbose):
+    from sqlalchemy.exc import IntegrityError
+
     from ..models import RoleModel
     from ..shared.const import ROLES_MAP
     from ..shared.utils import db
@@ -15,7 +17,10 @@ def register_roles_command(verbose):
     if len(roles_to_register) > 0:
         db.session.bulk_save_objects(roles_to_register)
 
-    db.session.commit()
+    try:
+        db.session.commit()
+    except IntegrityError:
+        db.session.rollback()
 
     if "postgres" in str(db.session.get_bind()):
         db.engine.execute(
diff --git a/cornflow/commands/schemas.py b/cornflow/commands/schemas.py
new file mode 100644
index 00000000..ecf99c51
--- /dev/null
+++ b/cornflow/commands/schemas.py
@@ -0,0 +1,33 @@
+def update_schemas_command(url, user, pwd, verbose):
+    import time
+
+    #
+    from cornflow_client.airflow.api import Airflow
+
+    from sqlalchemy.exc import IntegrityError
+    from ..models import DeployedDAG
+    from ..shared.utils import db
+
+    af_client = Airflow(url, user, pwd)
+    max_attempts = 20
+    attempts = 0
+    while not af_client.is_alive() and attempts < max_attempts:
+        attempts += 1
+        if verbose == 1:
+            print(f"Airflow is not reachable (attempt {attempts})")
+        time.sleep(15)
+
+    if not af_client.is_alive():
+        if verbose == 1:
+            print("Airflow is not reachable")
+        return False
+
+    response = af_client.update_schemas()
+    if response.status_code == 200:
+        if verbose == 1:
+            print("DAGs schemas updated")
+    else:
+        if verbose == 1:
+            print("The DAGs schemas were not updated properly")
+
+    return True
diff --git a/cornflow/commands/views.py b/cornflow/commands/views.py
index d7b18566..5923cf50 100644
--- a/cornflow/commands/views.py
+++ b/cornflow/commands/views.py
@@ -1,4 +1,6 @@
 def register_views_command(verbose):
+    from sqlalchemy.exc import IntegrityError
+
     from ..endpoints import resources
     from ..models import ApiViewModel
     from ..shared.utils import db
@@ -22,7 +24,10 @@ def register_views_command(verbose):
     if len(views_to_register) > 0:
         db.session.bulk_save_objects(views_to_register)
 
-    db.session.commit()
+    try:
+        db.session.commit()
+    except IntegrityError:
+        db.session.rollback()
 
     if "postgres" in str(db.session.get_bind()):
         db.engine.execute(
diff --git a/cornflow/config.py b/cornflow/config.py
index 57b46929..e53d4170 100644
--- a/cornflow/config.py
+++ b/cornflow/config.py
@@ -16,6 +16,9 @@ class DefaultConfig(object):
     DEBUG = True
     TESTING = True
 
+    # Open deployment (all dags accessible to all users)
+    OPEN_DEPLOYMENT = os.getenv("OPEN_DEPLOYMENT", 1)
+
     # LDAP configuration
     LDAP_HOST = os.getenv("LDAP_HOST", "ldap://openldap:389")
     LDAP_BIND_DN = os.getenv("LDAP_BIND_DN", "cn=admin,dc=example,dc=org")
diff --git a/cornflow/endpoints/case.py b/cornflow/endpoints/case.py
index ee209f59..04bfcc1c 100644
--- a/cornflow/endpoints/case.py
+++ b/cornflow/endpoints/case.py
@@ -59,6 +59,7 @@ def get(self, **kwargs):
 
     @doc(description="Create a new case from raw data", tags=["Cases"])
     @Auth.auth_required
+    @Auth.dag_permission_required
     @inflate
     @marshal_with(CaseListResponse)
     @use_kwargs(CaseRawRequest, location="json")
@@ -79,6 +80,7 @@ class CaseFromInstanceExecutionEndpoint(MetaResource, MethodResource):
 
     @doc(description="Create a new case from instance and execution", tags=["Cases"])
     @Auth.auth_required
+    @Auth.dag_permission_required
     @marshal_with(CaseListResponse)
     @use_kwargs(CaseFromInstanceExecution, location="json")
     def post(self, **kwargs):
@@ -196,6 +198,7 @@ def get(self, idx):
 
     @doc(description="Edit a case", tags=["Cases"])
     @Auth.auth_required
+    @Auth.dag_permission_required
     @use_kwargs(CaseEditRequest, location="json")
     def put(self, idx, **kwargs):
         """
@@ -257,6 +260,7 @@ def get(self, idx):
 
     @doc(description="Patches the data of a given case", tags=["Cases"], inherit=False)
     @Auth.auth_required
+    @Auth.dag_permission_required
     @inflate
     @use_kwargs(CaseCompareResponse, location="json")
     def patch(self, idx, **kwargs):
@@ -382,6 +386,8 @@ def get(self, idx1, idx2, **kwargs):
             payload["solution_patch"] = jsonpatch.make_patch(
                 case_1.solution, case_2.solution
             ).patch
+
+        payload["schema"] = case_1.schema
         log.debug(
             "User {} compared cases {} and {}".format(self.get_user_id(), idx1, idx2)
         )
diff --git a/cornflow/endpoints/execution.py b/cornflow/endpoints/execution.py
index 23265d40..0b070b84 100644
--- a/cornflow/endpoints/execution.py
+++ b/cornflow/endpoints/execution.py
@@ -76,6 +76,7 @@ def get(self, **kwargs):
 
     @doc(description="Create an execution", tags=["Executions"])
     @Auth.auth_required
+    @Auth.dag_permission_required
     @marshal_with(ExecutionDetailsEndpointResponse)
     @use_kwargs(ExecutionRequest, location="json")
     def post(self, **kwargs):
@@ -198,6 +199,7 @@ def get(self, idx):
 
     @doc(description="Edit an execution", tags=["Executions"], inherit=False)
     @Auth.auth_required
+    @Auth.dag_permission_required
     @use_kwargs(ExecutionEditRequest, location="json")
     def put(self, idx, **data):
         """
@@ -229,6 +231,7 @@ def delete(self, idx):
 
     @doc(description="Stop an execution", tags=["Executions"], inherit=False)
     @Auth.auth_required
+    @Auth.dag_permission_required
     def post(self, idx):
         execution = ExecutionModel.get_one_object_from_user(
             user=self.get_user(), idx=idx
diff --git a/cornflow/endpoints/instance.py b/cornflow/endpoints/instance.py
index 760ffad0..512ddc88 100644
--- a/cornflow/endpoints/instance.py
+++ b/cornflow/endpoints/instance.py
@@ -67,6 +67,7 @@ def get(self, **kwargs):
 
     @doc(description="Create an instance", tags=["Instances"])
     @Auth.auth_required
+    @Auth.dag_permission_required
     @inflate
     @marshal_with(InstanceDetailsEndpointResponse)
     @use_kwargs(InstanceRequest, location="json")
@@ -136,6 +137,7 @@ def get(self, idx):
 class InstanceDetailsEndpoint(InstanceDetailsEndpointBase):
     @doc(description="Edit an instance", tags=["Instances"])
     @Auth.auth_required
+    @Auth.dag_permission_required
     @use_kwargs(InstanceEditRequest, location="json")
     def put(self, idx, **data):
         """
diff --git a/cornflow/endpoints/login.py b/cornflow/endpoints/login.py
index 2ceda1e2..42c651f8 100644
--- a/cornflow/endpoints/login.py
+++ b/cornflow/endpoints/login.py
@@ -13,16 +13,13 @@
 # Import from internal modules
 from .meta_resource import MetaResource
 from ..models import UserModel, UserRoleModel
-from ..schemas.user import UserSchema, LoginEndpointRequest
+from ..schemas.user import LoginEndpointRequest
 from ..shared.authentication import Auth
 from ..shared.const import AUTH_DB, AUTH_LDAP
 from ..shared.exceptions import InvalidUsage, InvalidCredentials
 from ..shared.ldap import LDAP
 from ..shared.utils import db
 
-# Initialize the schema that the endpoint uses
-user_schema = UserSchema()
-
 
 class LoginEndpoint(MetaResource, MethodResource):
     """
diff --git a/cornflow/endpoints/signup.py b/cornflow/endpoints/signup.py
index e18eaa81..096df944 100644
--- a/cornflow/endpoints/signup.py
+++ b/cornflow/endpoints/signup.py
@@ -9,7 +9,7 @@
 
 # Import from internal modules
 from .meta_resource import MetaResource
-from ..models import UserModel, UserRoleModel
+from ..models import UserModel, PermissionsDAG, UserRoleModel
 from ..schemas.user import UserSignupRequest
 from ..shared.authentication import Auth
 from ..shared.const import AUTH_LDAP, PLANNER_ROLE
@@ -65,6 +65,9 @@ def post(self, **kwargs):
         user_role = UserRoleModel({"user_id": user.id, "role_id": PLANNER_ROLE})
         user_role.save()
 
+        if int(current_app.config["OPEN_DEPLOYMENT"]) == 1:
+            PermissionsDAG.add_all_permissions_to_user(user.id)
+
         try:
             token = Auth.generate_token(user.id)
         except Exception as e:
diff --git a/cornflow/models/__init__.py b/cornflow/models/__init__.py
index b07f2158..448a0895 100644
--- a/cornflow/models/__init__.py
+++ b/cornflow/models/__init__.py
@@ -4,8 +4,9 @@
 from .action import ActionModel
 from .apiview import ApiViewModel
 from .case import CaseModel
+from .dag import DeployedDAG
 from .execution import ExecutionModel
 from .instance import InstanceModel
-from .permission import PermissionViewRoleModel
+from .permission import PermissionsDAG, PermissionViewRoleModel
 from .roles import RoleModel, UserRoleModel
 from .user import UserModel
diff --git a/cornflow/models/dag.py b/cornflow/models/dag.py
new file mode 100644
index 00000000..8103cb5b
--- /dev/null
+++ b/cornflow/models/dag.py
@@ -0,0 +1,31 @@
+"""
+
+"""
+# Import from libraries
+from sqlalchemy.dialects.postgresql import TEXT
+
+# Import from internal modules
+from .meta_model import TraceAttributes
+from ..shared.utils import db
+
+
+class DeployedDAG(TraceAttributes):
+    """
+    This model contains the registry of the DAGs that are deployed on the corresponding Airflow server
+    """
+
+    __tablename__ = "deployed_dags"
+    id = db.Column(db.String(128), primary_key=True)
+    description = db.Column(TEXT, nullable=True)
+
+    def __init__(self, data):
+        super().__init__()
+        self.id = data.get("id")
+        self.description = data.get("description", None)
+
+    def __repr__(self):
+        return self.id
+
+    @staticmethod
+    def get_all_objects():
+        return DeployedDAG.query.all()
diff --git a/cornflow/models/permission.py b/cornflow/models/permission.py
index 85d42f00..c83913c4 100644
--- a/cornflow/models/permission.py
+++ b/cornflow/models/permission.py
@@ -1,4 +1,5 @@
 from .meta_model import TraceAttributes
+from .dag import DeployedDAG
 from ..shared.utils import db
 
 
@@ -41,3 +42,46 @@ def get_all_objects():
 
     def __repr__(self):
         return "{} can {} on {}".format(self.role_id, self.action_id, self.api_view_id)
+
+
+class PermissionsDAG(TraceAttributes):
+    __tablename__ = "permission_dag"
+    __table_args__ = (db.UniqueConstraint("dag_id", "user_id"),)
+
+    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
+
+    dag_id = db.Column(
+        db.String(128), db.ForeignKey("deployed_dags.id"), nullable=False
+    )
+    user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
+    user = db.relationship("UserModel", viewonly=True)
+
+    def __init__(self, data):
+        super().__init__()
+        self.dag_id = data.get("dag_id")
+        self.user_id = data.get("user_id")
+
+    def __repr__(self):
+        return f"User {self.user_id} can access {self.dag_id}"
+
+    @staticmethod
+    def get_all_objects():
+        return PermissionsDAG.query.all()
+
+    @staticmethod
+    def add_all_permissions_to_user(user_id):
+        dags = DeployedDAG.get_all_objects()
+        permissions = [
+            PermissionsDAG({"dag_id": dag.id, "user_id": user_id}) for dag in dags
+        ]
+        for permission in permissions:
+            permission.save()
+
+    @staticmethod
+    def check_if_has_permissions(user_id, dag_id):
+        permission = PermissionsDAG.query.filter_by(
+            user_id=user_id, dag_id=dag_id
+        ).first()
+        if permission is None:
+            return False
+        return True
diff --git a/cornflow/models/user.py b/cornflow/models/user.py
index b3f2fc55..f5fda57f 100644
--- a/cornflow/models/user.py
+++ b/cornflow/models/user.py
@@ -60,6 +60,12 @@ class UserModel(TraceAttributes):
     )
 
     user_roles = db.relationship("UserRoleModel", cascade="all,delete", backref="users")
+    dag_permissions = db.relationship(
+        "PermissionsDAG",
+        cascade="all,delete",
+        backref="users",
+        primaryjoin="and_(UserModel.id==PermissionsDAG.user_id)",
+    )
 
     @property
     def roles(self):
diff --git a/cornflow/schemas/case.py b/cornflow/schemas/case.py
index 74a2a88b..936066dd 100644
--- a/cornflow/schemas/case.py
+++ b/cornflow/schemas/case.py
@@ -60,6 +60,7 @@ class CaseFromInstanceExecution(Schema):
     name = fields.Str(required=True)
     description = fields.Str()
     parent_id = fields.Int(allow_none=True)
+    schema = fields.Str(required=True)
 
 
 class CaseToInstanceResponse(Schema):
@@ -78,6 +79,7 @@ class CaseEditRequest(Schema):
 class CaseCompareResponse(Schema):
     data_patch = fields.Nested(PatchOperation, many=True)
     solution_patch = fields.Nested(PatchOperation, many=True)
+    schema = fields.Str(required=True)
 
 
 class QueryFiltersCase(QueryFilters):
diff --git a/cornflow/schemas/execution.py b/cornflow/schemas/execution.py
index 2de7a6db..11ad66ea 100644
--- a/cornflow/schemas/execution.py
+++ b/cornflow/schemas/execution.py
@@ -63,6 +63,7 @@ class ExecutionRequest(Schema):
 class ExecutionEditRequest(Schema):
     name = fields.Str()
     description = fields.Str()
+    schema = fields.Str(required=True)
 
 
 class ExecutionDagRequest(Schema):
diff --git a/cornflow/schemas/instance.py b/cornflow/schemas/instance.py
index a6252987..8180070e 100644
--- a/cornflow/schemas/instance.py
+++ b/cornflow/schemas/instance.py
@@ -39,6 +39,7 @@ class InstanceFileRequest(Schema):
 class InstanceEditRequest(Schema):
     name = fields.Str()
     description = fields.Str()
+    schema = fields.Str(required=True)
 
 
 class InstanceEndpointResponse(BaseDataEndpointResponse):
diff --git a/cornflow/shared/authentication.py b/cornflow/shared/authentication.py
index ba3a68ee..b89f664a 100644
--- a/cornflow/shared/authentication.py
+++ b/cornflow/shared/authentication.py
@@ -3,9 +3,14 @@
 from functools import wraps
 import jwt
 
-from ..models import ApiViewModel, UserModel, UserRoleModel, PermissionViewRoleModel
+from ..models import ApiViewModel, UserModel, PermissionsDAG, PermissionViewRoleModel
 from ..shared.const import PERMISSION_METHOD_MAP
-from ..shared.exceptions import InvalidCredentials, ObjectDoesNotExist, NoPermission
+from ..shared.exceptions import (
+    InvalidCredentials,
+    InvalidData,
+    NoPermission,
+    ObjectDoesNotExist,
+)
 
 
 class Auth:
@@ -87,7 +92,10 @@ def get_permission_for_request(req, user_id):
         method, url = Auth.get_request_info(req)
         user_roles = UserModel.get_one_user(user_id).roles
         if user_roles is None or user_roles == {}:
-            raise NoPermission("You do not have permission to access this endpoint")
+            raise NoPermission(
+                error="You do not have permission to access this endpoint",
+                status_code=403,
+            )
 
         action_id = PERMISSION_METHOD_MAP[method]
         view_id = ApiViewModel.query.filter_by(url_rule=url).first().id
@@ -100,9 +108,10 @@ def get_permission_for_request(req, user_id):
             if has_permission:
                 return True
 
-        raise NoPermission("You do not have permission to access this endpoint")
+        raise NoPermission(
+            error="You do not have permission to access this endpoint", status_code=403
+        )
 
-    # user decorator
     @staticmethod
     def auth_required(func):
         """
@@ -120,6 +129,38 @@ def decorated_user(*args, **kwargs):
 
         return decorated_user
 
+    @staticmethod
+    def dag_permission_required(func):
+        """
+        DAG permission decorator
+        :param func:
+        :return:
+        """
+
+        @wraps(func)
+        def dag_decorator(*args, **kwargs):
+            if int(current_app.config["OPEN_DEPLOYMENT"]) == 0:
+                user_id = Auth.get_user_obj_from_header(request.headers).id
+                dag_id = request.json.get("schema", None)
+                if dag_id is None:
+                    raise InvalidData(
+                        error="The request does not specify a schema to use",
+                        status_code=400,
+                    )
+                else:
+                    if PermissionsDAG.check_if_has_permissions(user_id, dag_id):
+                        # We have permissions
+                        return func(*args, **kwargs)
+                    else:
+                        raise NoPermission(
+                            error="You do not have permission to use this DAG",
+                            status_code=403,
+                        )
+            else:
+                return func(*args, **kwargs)
+
+        return dag_decorator
+
     @staticmethod
     def return_user_from_token(token):
         user_id = Auth.decode_token(token)["user_id"]
diff --git a/init_cornflow_service.py b/init_cornflow_service.py
index 30f6acc3..91478a14 100644
--- a/init_cornflow_service.py
+++ b/init_cornflow_service.py
@@ -8,6 +8,9 @@
 from cornflow.shared.const import ADMIN_ROLE, AUTH_DB, SERVICE_ROLE
 from cornflow.app import create_app, db
 from cornflow.commands.access import access_init_command
+from cornflow.commands.dag import register_deployed_dags_command
+from cornflow.commands.permissions import register_dag_permissions_command
+from cornflow.commands.schemas import update_schemas_command
 from cornflow.commands.users import create_user_with_role
 
 os.chdir("/usr/src/app")
@@ -55,6 +58,9 @@
 CORNFLOW_LOGGING = os.getenv("CORNFLOW_LOGGING", "console")
 os.environ["CORNFLOW_LOGGING"] = CORNFLOW_LOGGING
 
+OPEN_DEPLOYMENT = os.getenv("OPEN_DEPLOYMENT", 1)
+os.environ["OPEN_DEPLOYMENT"] = str(OPEN_DEPLOYMENT)
+
 # Check LDAP parameters for active directory and show message
 if os.getenv("AUTH_TYPE") == 2:
     print(
@@ -111,6 +117,9 @@
             SERVICE_ROLE,
             verbose=1,
         )
+    register_deployed_dags_command(AIRFLOW_URL, AIRFLOW_USER, AIRFLOW_PWD, 1)
+    register_dag_permissions_command(OPEN_DEPLOYMENT, 1)
+    update_schemas_command(AIRFLOW_URL, AIRFLOW_USER, AIRFLOW_PWD, 1)
 
 # execute gunicorn application
 os.system(
diff --git a/migrations/versions/f3bee20314a2_.py b/migrations/versions/f3bee20314a2_.py
new file mode 100644
index 00000000..d729d478
--- /dev/null
+++ b/migrations/versions/f3bee20314a2_.py
@@ -0,0 +1,58 @@
+"""
+Added DAG master table and DAG permissions table
+
+Revision ID: f3bee20314a2
+Revises: ca449af8034c
+Create Date: 2021-12-14 14:41:16.096297
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "f3bee20314a2"
+down_revision = "ca449af8034c"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table(
+        "deployed_dags",
+        sa.Column("created_at", sa.DateTime(), nullable=False),
+        sa.Column("updated_at", sa.DateTime(), nullable=False),
+        sa.Column("deleted_at", sa.DateTime(), nullable=True),
+        sa.Column("id", sa.String(length=128), nullable=False),
+        sa.Column("description", sa.TEXT(), nullable=True),
+        sa.PrimaryKeyConstraint("id"),
+    )
+
+    op.create_table(
+        "permission_dag",
+        sa.Column("created_at", sa.DateTime(), nullable=False),
+        sa.Column("updated_at", sa.DateTime(), nullable=False),
+        sa.Column("deleted_at", sa.DateTime(), nullable=True),
+        sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
+        sa.Column("dag_id", sa.String(length=128), nullable=False),
+        sa.Column("user_id", sa.Integer(), nullable=False),
+        sa.ForeignKeyConstraint(
+            ["dag_id"],
+            ["deployed_dags.id"],
+        ),
+        sa.ForeignKeyConstraint(
+            ["user_id"],
+            ["users.id"],
+        ),
+        sa.PrimaryKeyConstraint("id"),
+        sa.UniqueConstraint("dag_id", "user_id"),
+    )
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_table("permission_dag")
+    op.drop_table("deployed_dags")
+    # ### end Alembic commands ###
diff --git a/requirements.txt b/requirements.txt
index 726767b9..1dce9649 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ certifi
 cffi
 chardet
 click
-cornflow-client @ git+https://github.com/baobabsoluciones/cornflow-client@master
+cornflow-client @ git+https://github.com/baobabsoluciones/cornflow-client@feature/list_dags
 cryptography
 Flask
 flask-apispec


Test patch:
diff --git a/.github/workflows/run_tests_airflow.yml b/.github/workflows/run_tests_airflow.yml
index 0c39f06a..0e73f597 100644
--- a/.github/workflows/run_tests_airflow.yml
+++ b/.github/workflows/run_tests_airflow.yml
@@ -28,7 +28,7 @@ jobs:
       run: |
         python -m venv afvenv
         source afvenv/bin/activate
-        AIRFLOW_VERSION=2.1.0
+        AIRFLOW_VERSION=2.2.1
         PYTHON_VERSION="$(python3 --version | cut -d " " -f 2 | cut -d "." -f 1-2)"
         CONSTRAINT_URL="https://raw.githubusercontent.com/apache/airflow/constraints-${AIRFLOW_VERSION}/constraints-${PYTHON_VERSION}.txt"
         python -m pip install "apache-airflow==${AIRFLOW_VERSION}" --constraint "${CONSTRAINT_URL}"
diff --git a/cornflow/tests/custom_liveServer.py b/cornflow/tests/custom_liveServer.py
index 8f826b7a..595565bb 100644
--- a/cornflow/tests/custom_liveServer.py
+++ b/cornflow/tests/custom_liveServer.py
@@ -7,6 +7,9 @@
 # Internal modules
 from cornflow.app import create_app, access_init
 from cornflow.models import UserRoleModel
+from cornflow.commands.access import access_init_command
+from cornflow.commands.dag import register_deployed_dags_command_test
+from cornflow.commands.permissions import register_dag_permissions_command
 from cornflow.shared.const import ADMIN_ROLE, SERVICE_ROLE
 from cornflow.shared.utils import db
 from cornflow.tests.const import PREFIX
@@ -31,8 +34,8 @@ def login_or_signup(self, user_data):
     def setUp(self, create_all=True):
         if create_all:
             db.create_all()
-        self.runner = create_app().test_cli_runner()
-        self.runner.invoke(access_init)
+        access_init_command(0)
+        register_deployed_dags_command_test(verbose=0)
         user_data = dict(
             username="testname",
             email="test@test.com",
@@ -44,6 +47,7 @@ def setUp(self, create_all=True):
         self.url = None
         self.model = None
         self.items_to_check = []
+        register_dag_permissions_command(open_deployment=1, verbose=0)
 
     def tearDown(self):
         db.session.remove()
diff --git a/cornflow/tests/custom_test_case.py b/cornflow/tests/custom_test_case.py
index b2b06ec4..ef24afc4 100644
--- a/cornflow/tests/custom_test_case.py
+++ b/cornflow/tests/custom_test_case.py
@@ -11,8 +11,11 @@
 from unittest.mock import patch, Mock
 
 # Import from internal modules
-from cornflow.app import create_app, access_init
+from cornflow.app import create_app
 from cornflow.models import UserRoleModel
+from cornflow.commands.access import access_init_command
+from cornflow.commands.dag import register_deployed_dags_command_test
+from cornflow.commands.permissions import register_dag_permissions_command
 from cornflow.shared.authentication import Auth
 from cornflow.shared.const import ADMIN_ROLE, PLANNER_ROLE, SERVICE_ROLE
 from cornflow.shared.utils import db
@@ -48,8 +51,8 @@ def load_file(_file, fk=None, fk_id=None):
 
     def setUp(self):
         db.create_all()
-        self.runner = create_app().test_cli_runner()
-        self.runner.invoke(access_init)
+        access_init_command(0)
+        register_deployed_dags_command_test(verbose=0)
         data = {
             "username": "testname",
             "email": "test@test.com",
@@ -63,6 +66,8 @@ def setUp(self):
             headers={"Content-Type": "application/json"},
         )
 
+        register_dag_permissions_command(open_deployment=1, verbose=0)
+
         data.pop("email")
 
         self.token = self.client.post(
@@ -152,6 +157,7 @@ def create_new_row(
             follow_redirects=True,
             headers=self.get_header_with_auth(token),
         )
+
         self.assertEqual(expected_status, response.status_code)
         if not check_payload:
             return response.json
@@ -436,7 +442,11 @@ def test_update_one_row(self):
                 self.url_with_query_arguments(), self.model, self.payload
             )
             payload = {**self.payload, **dict(id=idx, name="new_name")}
-            self.update_row(self.url + str(idx) + "/", dict(name="new_name"), payload)
+            self.update_row(
+                self.url + str(idx) + "/",
+                dict(name="new_name", schema=payload["schema"]),
+                payload,
+            )
 
         def test_update_one_row_bad_format(self):
             idx = self.create_new_row(
@@ -636,10 +646,10 @@ def test_old_token(self):
                 headers={"Content-Type": "application/json"},
             )
 
-            self.id = response.json["id"]
+            self.idx = response.json["id"]
 
             response = self.client.get(
-                USER_URL + str(self.id) + "/",
+                USER_URL + str(self.idx) + "/",
                 follow_redirects=True,
                 headers={
                     "Content-Type": "application/json",
@@ -661,10 +671,10 @@ def test_bad_format_token(self):
             )
 
             token = response.json["token"]
-            self.id = response.json["id"]
+            self.idx = response.json["id"]
 
             response = self.client.get(
-                USER_URL + str(self.id) + "/",
+                USER_URL + str(self.idx) + "/",
                 follow_redirects=True,
                 headers={
                     "Content-Type": "application/json",
@@ -687,10 +697,10 @@ def test_invalid_token(self):
                 follow_redirects=True,
                 headers={"Content-Type": "application/json"},
             )
-            self.id = response.json["id"]
+            self.idx = response.json["id"]
 
             response = self.client.get(
-                USER_URL + str(self.id) + "/",
+                USER_URL + str(self.idx) + "/",
                 follow_redirects=True,
                 headers={
                     "Content-Type": "application/json",
diff --git a/cornflow/tests/data/full_case_patch.json b/cornflow/tests/data/full_case_patch.json
index 1aab6658..413a4bdb 100644
--- a/cornflow/tests/data/full_case_patch.json
+++ b/cornflow/tests/data/full_case_patch.json
@@ -57,5 +57,6 @@
             "path": "/assignment/42/color",
             "value": 3
         }
-    ]
+    ],
+    "schema": "gc"
 }
\ No newline at end of file
diff --git a/cornflow/tests/data/json_patch_bad.json b/cornflow/tests/data/json_patch_bad.json
index 91802ef8..c6a9ac4d 100644
--- a/cornflow/tests/data/json_patch_bad.json
+++ b/cornflow/tests/data/json_patch_bad.json
@@ -31,5 +31,6 @@
       "path": "/name",
       "value": "test_case_2"
     }
-  ]
+  ],
+  "schema": "solve_model_dag"
 }
\ No newline at end of file
diff --git a/cornflow/tests/data/json_patch_good.json b/cornflow/tests/data/json_patch_good.json
index c605d456..e734eace 100644
--- a/cornflow/tests/data/json_patch_good.json
+++ b/cornflow/tests/data/json_patch_good.json
@@ -20,5 +20,6 @@
       "path": "/constraints/2/constant",
       "value": -10
     }
-  ]
+  ],
+  "schema": "solve_model_dag"
 }
\ No newline at end of file
diff --git a/cornflow/tests/data/new_instance_2.json b/cornflow/tests/data/new_instance_2.json
index 39567bdd..ff879a94 100644
--- a/cornflow/tests/data/new_instance_2.json
+++ b/cornflow/tests/data/new_instance_2.json
@@ -122,4 +122,5 @@
     "sos2": []
 },
 "name":"instance_2",
-"description":"This is a test instance to check if name and description get saved succesfully"}
\ No newline at end of file
+"description":"This is a test instance to check if name and description get saved succesfully",
+"schema":  "solve_model_dag"}
\ No newline at end of file
diff --git a/cornflow/tests/integration/test_cornflowclient.py b/cornflow/tests/integration/test_cornflowclient.py
index 40d2d58f..c87a68ca 100644
--- a/cornflow/tests/integration/test_cornflowclient.py
+++ b/cornflow/tests/integration/test_cornflowclient.py
@@ -56,7 +56,7 @@ def create_new_instance(self, mps_file):
         description = "description123"
         data = pulp.LpProblem.fromMPS(mps_file, sense=1)[1].toDict()
         schema = "solve_model_dag"
-        payload = dict(data=data, name=name, description=description)
+        payload = dict(data=data, name=name, description=description, schema=schema)
         return self.create_new_instance_payload(payload)
 
     def create_new_instance_payload(self, payload):
@@ -290,19 +290,19 @@ def setUp(self, create_all=False):
     #     self.assertEqual(status["state"], EXEC_STATE_CORRECT)
     #     self.assertEqual(results["state"], EXEC_STATE_CORRECT)
 
-    def test_interrupt(self):
-        execution = self.create_timer_instance_and_execution(5)
-        self.client.stop_execution(execution_id=execution["id"])
-        time.sleep(2)
-        status = self.client.get_status(execution["id"])
-        results = self.client.get_results(execution["id"])
-        self.assertEqual(status["state"], EXEC_STATE_STOPPED)
-        self.assertEqual(results["state"], EXEC_STATE_STOPPED)
-
-    def test_status_solving(self):
-        execution = self.create_timer_instance_and_execution(5)
-        status = self.client.get_status(execution["id"])
-        self.assertEqual(status["state"], EXEC_STATE_RUNNING)
+    # def test_interrupt(self):
+    #     execution = self.create_timer_instance_and_execution(5)
+    #     self.client.stop_execution(execution_id=execution["id"])
+    #     time.sleep(2)
+    #     status = self.client.get_status(execution["id"])
+    #     results = self.client.get_results(execution["id"])
+    #     self.assertEqual(status["state"], EXEC_STATE_STOPPED)
+    #     self.assertEqual(results["state"], EXEC_STATE_STOPPED)
+    #
+    # def test_status_solving(self):
+    #     execution = self.create_timer_instance_and_execution(5)
+    #     status = self.client.get_status(execution["id"])
+    #     self.assertEqual(status["state"], EXEC_STATE_RUNNING)
 
     def test_manual_execution(self):
 
@@ -354,9 +354,13 @@ def test_manual_execution2(self):
     def test_edit_one_execution(self):
         one_instance = self.create_new_instance("./cornflow/tests/data/test_mps.mps")
         payload = dict(
-            name="bla", config=dict(solver="CBC"), instance_id=one_instance["id"]
+            name="bla",
+            config=dict(solver="CBC"),
+            instance_id=one_instance["id"],
+            schema="solve_model_dag",
         )
         execution = self.client.create_api("execution/?run=0", json=payload)
+        print(execution.json())
         payload = dict(log_text="")
         response = self.client.put_api_for_id(
             api="dag/", id=execution.json()["id"], payload=payload
diff --git a/cornflow/tests/unit/test_cases.py b/cornflow/tests/unit/test_cases.py
index d6df3507..59805795 100644
--- a/cornflow/tests/unit/test_cases.py
+++ b/cornflow/tests/unit/test_cases.py
@@ -125,6 +125,7 @@ def setUp(self):
             "description": "test case for unit tests",
             "instance_id": instance_id,
             "execution_id": execution_id,
+            "schema": "solve_model_dag",
         }
         self.user_object = UserModel.get_one_user(self.user)
         self.instance = InstanceModel.get_one_object_from_user(
@@ -394,7 +395,8 @@ def setUp(self):
         self.patch = {
             "data_patch": jsonpatch.make_patch(
                 self.payloads[0]["data"], self.payloads[1]["data"]
-            ).patch
+            ).patch,
+            "schema": self.payload["schema"],
         }
         self.patch_file = self.load_file(JSON_PATCH_GOOD_PATH)
 
@@ -548,6 +550,7 @@ def setUp(self):
         self.cases_id = [
             self.create_new_row(self.url, self.model, p) for p in self.payloads
         ]
+
         self.items_to_check = ["name", "description", "schema"]
 
     def test_get_full_patch(self):
diff --git a/cornflow/tests/unit/test_log_in.py b/cornflow/tests/unit/test_log_in.py
index 6b28bee5..81618386 100644
--- a/cornflow/tests/unit/test_log_in.py
+++ b/cornflow/tests/unit/test_log_in.py
@@ -26,8 +26,8 @@ def setUp(self):
         db.session.commit()
         # we take out the email, we do not need it to log in
         self.data.pop("email")
-        self.id = UserModel.query.filter_by(username="testname").first().id
+        self.idx = UserModel.query.filter_by(username="testname").first().id
 
     def test_successful_log_in(self):
         super().test_successful_log_in()
-        self.assertEqual(self.id, self.response.json["id"])
+        self.assertEqual(self.idx, self.response.json["id"])
diff --git a/cornflow/tests/unit/test_users.py b/cornflow/tests/unit/test_users.py
index f6abf8d2..aba64d11 100644
--- a/cornflow/tests/unit/test_users.py
+++ b/cornflow/tests/unit/test_users.py
@@ -1,7 +1,10 @@
 import json
 
 from flask_testing import TestCase
-from cornflow.app import create_app, access_init
+from cornflow.app import create_app
+from cornflow.commands.access import access_init_command
+from cornflow.commands.dag import register_deployed_dags_command_test
+from cornflow.commands.permissions import register_dag_permissions_command
 from cornflow.models import (
     CaseModel,
     ExecutionModel,
@@ -31,8 +34,8 @@ def create_app(self):
 
     def setUp(self):
         db.create_all()
-        self.runner = create_app().test_cli_runner()
-        self.runner.invoke(access_init)
+        access_init_command(0)
+        register_deployed_dags_command_test(verbose=0)
 
         self.url = USER_URL
         self.model = UserModel
@@ -58,7 +61,9 @@ def setUp(self):
         )
 
         self.admin_2 = dict(
-            username="aSecondAdmin", email="admin2@admin2.com", password="Testpassword2!"
+            username="aSecondAdmin",
+            email="admin2@admin2.com",
+            password="Testpassword2!",
         )
 
         self.service_user = dict(
@@ -121,6 +126,7 @@ def setUp(self):
                 user_role.save()
 
         db.session.commit()
+        register_dag_permissions_command(verbose=0)
 
     def tearDown(self):
         db.session.remove()
@@ -350,8 +356,8 @@ def create_app(self):
 
     def setUp(self):
         db.create_all()
-        self.runner = create_app().test_cli_runner()
-        self.runner.invoke(access_init)
+        access_init_command(0)
+        register_deployed_dags_command_test(verbose=0)
 
         self.url = USER_URL
         self.model = UserModel
@@ -387,6 +393,7 @@ def setUp(self):
         )
 
         self.viewer["id"] = response.json["id"]
+        register_dag_permissions_command(verbose=0)
 
     def tearDown(self):
         db.session.remove()