Example #1
0
    def _sync_dag_view_permissions(self, dag_id, access_control):
        """
        Set the access policy on the given DAG's ViewModel.

        :param dag_id: the ID of the DAG whose permissions should be updated
        :type dag_id: str
        :param access_control: a dict where each key is a rolename and
            each value is a set() of permission names (e.g.,
            {'can_read'}
        :type access_control: dict
        """
        dag_resource_name = permissions.resource_name_for_dag(dag_id)

        def _get_or_create_dag_permission(perm_name):
            dag_perm = self.find_permission_view_menu(perm_name, dag_resource_name)
            if not dag_perm:
                self.log.info("Creating new permission '%s' on view '%s'", perm_name, dag_resource_name)
                dag_perm = self.add_permission_view_menu(perm_name, dag_resource_name)

            return dag_perm

        def _revoke_stale_permissions(dag_view):
            existing_dag_perms = self.find_permissions_view_menu(dag_view)
            for perm in existing_dag_perms:
                non_admin_roles = [role for role in perm.role if role.name != 'Admin']
                for role in non_admin_roles:
                    target_perms_for_role = access_control.get(role.name, {})
                    if perm.permission.name not in target_perms_for_role:
                        self.log.info(
                            "Revoking '%s' on DAG '%s' for role '%s'",
                            perm.permission,
                            dag_resource_name,
                            role.name,
                        )
                        self.del_permission_role(role, perm)

        dag_view = self.find_view_menu(dag_resource_name)
        if dag_view:
            _revoke_stale_permissions(dag_view)

        for rolename, perms in access_control.items():
            role = self.find_role(rolename)
            if not role:
                raise AirflowException(
                    "The access_control mapping for DAG '{}' includes a role "
                    "named '{}', but that role does not exist".format(dag_id, rolename)
                )

            perms = set(perms)
            invalid_perms = perms - self.DAG_PERMS
            if invalid_perms:
                raise AirflowException(
                    "The access_control map for DAG '{}' includes the following "
                    "invalid permissions: {}; The set of valid permissions "
                    "is: {}".format(dag_resource_name, invalid_perms, self.DAG_PERMS)
                )

            for perm_name in perms:
                dag_perm = _get_or_create_dag_permission(perm_name)
                self.add_permission_role(role, dag_perm)
Example #2
0
    def _sync_dag_view_permissions(self, dag_id, access_control):
        """
        Set the access policy on the given DAG's ViewModel.

        :param dag_id: the ID of the DAG whose permissions should be updated
        :param access_control: a dict where each key is a rolename and
            each value is a set() of action names (e.g. {'can_read'})
        """
        dag_resource_name = permissions.resource_name_for_dag(dag_id)

        def _get_or_create_dag_permission(
                action_name: str) -> Optional[Permission]:
            perm = self.get_permission(action_name, dag_resource_name)
            if not perm:
                self.log.info("Creating new action '%s' on resource '%s'",
                              action_name, dag_resource_name)
                perm = self.create_permission(action_name, dag_resource_name)

            return perm

        def _revoke_stale_permissions(resource: Resource):
            existing_dag_perms = self.get_resource_permissions(resource)
            for perm in existing_dag_perms:
                non_admin_roles = [
                    role for role in perm.role if role.name != 'Admin'
                ]
                for role in non_admin_roles:
                    target_perms_for_role = access_control.get(role.name, {})
                    if perm.action.name not in target_perms_for_role:
                        self.log.info(
                            "Revoking '%s' on DAG '%s' for role '%s'",
                            perm.action,
                            dag_resource_name,
                            role.name,
                        )
                        self.remove_permission_from_role(role, perm)

        resource = self.get_resource(dag_resource_name)
        if resource:
            _revoke_stale_permissions(resource)

        for rolename, action_names in access_control.items():
            role = self.find_role(rolename)
            if not role:
                raise AirflowException(
                    f"The access_control mapping for DAG '{dag_id}' includes a role named "
                    f"'{rolename}', but that role does not exist")

            action_names = set(action_names)
            invalid_action_names = action_names - self.DAG_ACTIONS
            if invalid_action_names:
                raise AirflowException(
                    f"The access_control map for DAG '{dag_resource_name}' includes "
                    f"the following invalid permissions: {invalid_action_names}; "
                    f"The set of valid permissions is: {self.DAG_ACTIONS}")

            for action_name in action_names:
                dag_perm = _get_or_create_dag_permission(action_name)
                if dag_perm:
                    self.add_permission_to_role(role, dag_perm)
Example #3
0
 def can_delete_dag(self, dag_id, user=None) -> bool:
     """Determines whether a user has DAG delete access."""
     root_dag_id = self._get_root_dag_id(dag_id)
     dag_resource_name = permissions.resource_name_for_dag(root_dag_id)
     return self.has_access(permissions.ACTION_CAN_DELETE,
                            dag_resource_name,
                            user=user)
Example #4
0
 def can_read_dag(self, dag_id, user=None) -> bool:
     """Determines whether a user has DAG read access."""
     if not user:
         user = g.user
     dag_resource_name = permissions.resource_name_for_dag(dag_id)
     return self._has_view_access(
         user, permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG
     ) or self._has_view_access(user, permissions.ACTION_CAN_READ, dag_resource_name)
Example #5
0
 def needs_perms(dag_id: str) -> bool:
     dag_resource_name = resource_name_for_dag(dag_id)
     for permission_name in DAG_ACTIONS:
         if not (session.query(Permission).join(Action).join(Resource).
                 filter(Action.name == permission_name).filter(
                     Resource.name == dag_resource_name).one_or_none()):
             return True
     return False
Example #6
0
 def prefixed_dag_id(self, dag_id):
     """Returns the permission name for a DAG id."""
     warnings.warn(
         "`prefixed_dag_id` has been deprecated. "
         "Please use `airflow.security.permissions.resource_name_for_dag` instead.",
         DeprecationWarning,
         stacklevel=2,
     )
     return permissions.resource_name_for_dag(dag_id)
Example #7
0
    def can_access_some_dags(self, action: str, dag_id: Optional[str] = None) -> bool:
        """Checks if user has read or write access to some dags."""
        if dag_id and dag_id != '~':
            return self.has_access(action, permissions.resource_name_for_dag(dag_id))

        user = g.user
        if action == permissions.ACTION_CAN_READ:
            return any(self.get_readable_dags(user))
        return any(self.get_editable_dags(user))
Example #8
0
 def can_read_dag(self, dag_id, user=None) -> bool:
     """Determines whether a user has DAG read access."""
     if not user:
         user = g.user
     # To account for SubDags
     root_dag_id = dag_id.split(".")[0]
     dag_resource_name = permissions.resource_name_for_dag(root_dag_id)
     return self._has_access(
         user, permissions.ACTION_CAN_READ, permissions.RESOURCE_DAG
     ) or self._has_access(user, permissions.ACTION_CAN_READ, dag_resource_name)
Example #9
0
 def needs_perm_views(dag_id: str) -> bool:
     dag_resource_name = resource_name_for_dag(dag_id)
     for permission_name in DAG_PERMS:
         if not (session.query(sqla_models.PermissionView).join(
                 sqla_models.Permission).join(
                     sqla_models.ViewMenu).filter(
                         sqla_models.Permission.name == permission_name
                     ).filter(sqla_models.ViewMenu.name ==
                              dag_resource_name).one_or_none()):
             return True
     return False
Example #10
0
    def test_create_dag_specific_permissions(self, dagbag_mock):
        access_control = {'Public': {permissions.ACTION_CAN_READ}}
        dags = [
            DAG('has_access_control', access_control=access_control),
            DAG('no_access_control'),
        ]

        collect_dags_from_db_mock = mock.Mock()
        dagbag = mock.Mock()

        dagbag.dags = {dag.dag_id: dag for dag in dags}
        dagbag.collect_dags_from_db = collect_dags_from_db_mock
        dagbag_mock.return_value = dagbag

        self.security_manager._sync_dag_view_permissions = mock.Mock()

        for dag in dags:
            dag_resource_name = permissions.resource_name_for_dag(dag.dag_id)
            all_perms = self.security_manager.get_all_permissions()
            assert ('can_read', dag_resource_name) not in all_perms
            assert ('can_edit', dag_resource_name) not in all_perms

        self.security_manager.create_dag_specific_permissions()

        dagbag_mock.assert_called_once_with(read_dags_from_db=True)
        collect_dags_from_db_mock.assert_called_once_with()

        for dag in dags:
            dag_resource_name = permissions.resource_name_for_dag(dag.dag_id)
            all_perms = self.security_manager.get_all_permissions()
            assert ('can_read', dag_resource_name) in all_perms
            assert ('can_edit', dag_resource_name) in all_perms

        self.security_manager._sync_dag_view_permissions.assert_called_once_with(
            permissions.resource_name_for_dag('has_access_control'),
            access_control)

        del dagbag.dags["has_access_control"]
        with assert_queries_count(
                1):  # one query to get all perms; dagbag is mocked
            self.security_manager.create_dag_specific_permissions()
Example #11
0
    def sync_perm_for_dag(self, dag_id, access_control=None):
        """
        Sync permissions for given dag id. The dag id surely exists in our dag bag
        as only / refresh button or DagBag will call this function

        :param dag_id: the ID of the DAG whose permissions should be updated
        :param access_control: a dict where each key is a rolename and
            each value is a set() of action names (e.g.,
            {'can_read'}
        :return:
        """
        dag_resource_name = permissions.resource_name_for_dag(dag_id)
        for dag_action_name in self.DAG_ACTIONS:
            self.create_permission(dag_action_name, dag_resource_name)

        if access_control:
            self._sync_dag_view_permissions(dag_resource_name, access_control)
Example #12
0
    def create_dag_specific_permissions(self) -> None:
        """
        Creates 'can_read' and 'can_edit' permissions for all DAGs,
        along with any `access_control` permissions provided in them.

        This does iterate through ALL the DAGs, which can be slow. See `sync_perm_for_dag`
        if you only need to sync a single DAG.

        :return: None.
        """
        perms = self.get_all_permissions()
        dagbag = DagBag(read_dags_from_db=True)
        dagbag.collect_dags_from_db()
        dags = dagbag.dags.values()

        for dag in dags:
            dag_resource_name = permissions.resource_name_for_dag(dag.dag_id)
            for perm_name in self.DAG_PERMS:
                if (perm_name, dag_resource_name) not in perms:
                    self._merge_perm(perm_name, dag_resource_name)

            if dag.access_control:
                self._sync_dag_view_permissions(dag_resource_name, dag.access_control)
Example #13
0
 def _has_dag_perm(self, perm, dag_id, user):
     # if not user:
     #     user = self.user
     return self.security_manager.has_access(
         perm, permissions.resource_name_for_dag(dag_id), user)
Example #14
0
 def can_edit_dag(self, dag_id, user=None) -> bool:
     """Determines whether a user has DAG edit access."""
     dag_resource_name = permissions.resource_name_for_dag(dag_id)
     return self.has_access(permissions.ACTION_CAN_EDIT,
                            dag_resource_name,
                            user=user)