Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AuthZ: Extend /api/search to work with self-contained permissions #70749

Merged
merged 18 commits into from Jul 12, 2023

Conversation

mgyongyosi
Copy link
Contributor

@mgyongyosi mgyongyosi commented Jun 27, 2023

What is this feature?
This PR extends the accessControlDashboardPermissionFilter (used by the /api/search endpoint) to be capable of handling users authenticated by the Extended JWT client (in which the SignedInUser's permissions are populated from the entitlement claim of the access token) and these changes make sure that the search result only contains dashboards and folders that the SignedInUser has access to.

Besides, this PR adds a new field to the SignedInUser called AuthenticatedBy which stores the name of the Auth Client that authenticated the current user.

Why do we need this feature?
The dashboard filters should handle the case when the permissions are only set in the SignedInUser's Permissions field and this field is the source of truth for the users' permission which is the case for Extended JWT Client.

Used by the experimental external service communication feature (which is using OAuth2 in the background).

Who is this feature for?

[Add information on what kind of user the feature is for.]

Which issue(s) does this PR fix?:

Fixes #

Special notes for your reviewer:
If the user has the following Permissions:

{ "action": "dashboards:read", "scope": "dashboards:uid:d5485b09-0b3b-47d0-a353-ec6035e1cfdf" },
{ "action": "dashboards:read", "scope": "folders:uid:ce0d1d61-7043-4fb0-9fbc-b5d15963fce0"},
{ "action": "folders:read", "scope": "folders:uid:ba1313d4-d404-48ec-a088-2eea38e4bd29"},

Then the generated SQL will be

nestedFolders = on
WITH RECURSIVE RecQry0 AS (
    SELECT
        uid,
        parent_uid,
        org_id
    FROM
        folder
    WHERE
        uid IN ('ce0d1d61-7043-4fb0-9fbc-b5d15963fce0')
    UNION
    ALL
    SELECT
        f.uid,
        f.parent_uid,
        f.org_id
    FROM
        folder f
        INNER JOIN RecQry0 r ON f.parent_uid = r.uid
        and f.org_id = r.org_id
),
RecQry1 AS (
    SELECT
        uid,
        parent_uid,
        org_id
    FROM
        folder
    WHERE
        uid IN ('ba1313d4-d404-48ec-a088-2eea38e4bd29')
    UNION
    ALL
    SELECT
        f.uid,
        f.parent_uid,
        f.org_id
    FROM
        folder f
        INNER JOIN RecQry1 r ON f.parent_uid = r.uid
        and f.org_id = r.org_id
)
SELECT
    dashboard.id,
    dashboard.uid,
    dashboard.title,
    dashboard.slug,
    dashboard_tag.term,
    dashboard.is_folder,
    dashboard.folder_id,
    folder.uid AS folder_uid,
    folder.slug AS folder_slug,
    folder.title AS folder_title
FROM
    (
        SELECT
            dashboard.id
        FROM
            dashboard
        WHERE
            (
                (
                    dashboard.uid IN ('d5485b09-0b3b-47d0-a353-ec6035e1cfdf')
                    AND NOT dashboard.is_folder
                )
                OR (
                    dashboard.folder_id IN (
                        SELECT
                            d.id
                        FROM
                            dashboard as d
                        WHERE
                            d.uid IN (
                                SELECT
                                    uid
                                FROM
                                    RecQry0
                            )
                    )
                    AND NOT dashboard.is_folder
                )
                OR (
                    dashboard.uid IN (
                        SELECT
                            uid
                        FROM
                            RecQry1
                    )
                    AND dashboard.is_folder
                )
            )
            AND dashboard.org_id = 1
        ORDER BY
            dashboard.title ASC
        LIMIT
            1000 OFFSET 0
    ) AS ids
    INNER JOIN dashboard ON ids.id = dashboard.id
    LEFT OUTER JOIN dashboard AS folder ON folder.id = dashboard.folder_id
    LEFT OUTER JOIN dashboard_tag ON dashboard.id = dashboard_tag.dashboard_id
ORDER BY
    dashboard.title ASC
nestedFolders = off
SELECT
    dashboard.id,
    dashboard.uid,
    dashboard.title,
    dashboard.slug,
    dashboard_tag.term,
    dashboard.is_folder,
    dashboard.folder_id,
    folder.uid AS folder_uid,
    folder.slug AS folder_slug,
    folder.title AS folder_title
FROM
    (
        SELECT
            dashboard.id
        FROM
            dashboard
        WHERE
            (
                (
                    dashboard.uid IN ('d5485b09-0b3b-47d0-a353-ec6035e1cfdf')
                    AND NOT dashboard.is_folder
                )
                OR (
                    dashboard.folder_id IN (
                        SELECT
                            d.id
                        FROM
                            dashboard as d
                        WHERE
                            d.uid IN ('ce0d1d61-7043-4fb0-9fbc-b5d15963fce0')
                    )
                    AND NOT dashboard.is_folder
                )
                OR (
                    dashboard.uid IN ('ba1313d4-d404-48ec-a088-2eea38e4bd29')
                    AND dashboard.is_folder
                )
            )
            AND dashboard.org_id = 1
        ORDER BY
            dashboard.title ASC
        LIMIT
            1000 OFFSET 0
    ) AS ids
    INNER JOIN dashboard ON ids.id = dashboard.id
    LEFT OUTER JOIN dashboard AS folder ON folder.id = dashboard.folder_id
    LEFT OUTER JOIN dashboard_tag ON dashboard.id = dashboard_tag.dashboard_id
ORDER BY
    dashboard.title ASC

Please check that:

  • It works as expected from a user's perspective.
  • If this is a pre-GA feature, it is behind a feature toggle.
  • The docs are updated, and if this is a notable improvement, it's added to our What's New doc.

@mgyongyosi mgyongyosi added this to the 10.1.x milestone Jun 27, 2023
@mgyongyosi mgyongyosi changed the title AuthZ: Extend /api/search to work with self-contained permissions WIP AuthZ: Extend /api/search to work with self-contained permissions Jun 27, 2023
@mgyongyosi mgyongyosi added the area/auth/rbac Grafana role-based access control label Jun 27, 2023
@mgyongyosi mgyongyosi marked this pull request as ready for review July 4, 2023 07:50
@mgyongyosi mgyongyosi requested review from a team as code owners July 4, 2023 07:50
@mgyongyosi mgyongyosi requested review from mildwonkey, suntala, yangkb09 and kalleep and removed request for a team July 4, 2023 07:50
@mgyongyosi mgyongyosi changed the title WIP AuthZ: Extend /api/search to work with self-contained permissions AuthZ: Extend /api/search to work with self-contained permissions Jul 4, 2023
@mgyongyosi mgyongyosi requested a review from gamab July 5, 2023 09:09
Copy link
Contributor

@IevaVasiljeva IevaVasiljeva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some tiny comments about tests, but I also have questions about the query logic. Let's catch up at some point to talk it through.

pkg/services/authn/clients/grafana_test.go Outdated Show resolved Hide resolved
pkg/services/login/authinfo.go Show resolved Hide resolved
@papagian
Copy link
Contributor

papagian commented Jul 6, 2023

hi @mgyongyosi
I'm working on measuring and refactoring the search query for mitigating performance issues.
from browsing the changes, I get that the only change in the accessControlDashboardPermissionFilter is the additional check for useSelfContainedPermissions and the additional implementation when useSelfContainedPermissions=true.
is that correct?

@mgyongyosi
Copy link
Contributor Author

mgyongyosi commented Jul 6, 2023

hi @mgyongyosi I'm working on measuring and refactoring the search query for mitigating performance issues. from browsing the changes, I get that the only change in the accessControlDashboardPermissionFilter is the additional check for useSelfContainedPermissions and the additional implementation when useSelfContainedPermissions=true. is that correct?

@papagian Correct, but based on @kalleep's feedback, I'm gonna change the useSelfContainedPermissions=true part a little bit to use the accesscontrol.Filter method instead of having my own implementation (I still need to test it whether it works with the Filter method, I'll let you know), but I won't change the part when useSelfContainedPermissions=false!

@mgyongyosi mgyongyosi force-pushed the mgyongyosi/sql-filters-empty-role branch from b97ab89 to 0ba0f85 Compare July 6, 2023 15:23
return result
}

func parseArguments(actions []string, user *user.SignedInUser, scopePrefix string) []interface{} {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic would list all the dashboards that user has any of the required permissions on, while the old logic was listing dashboards that user has all of the required permissions on. Or am I misunderstanding something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I investigated this and you are right! I pushed a fix + added tests for this scenario!

Copy link
Contributor

@gamab gamab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍
I left a couple of comments/suggestions, the cyclomatic complexity of the buildClause function is what worries me most. I'd consider splitting it 🤔

pkg/services/authn/authn.go Outdated Show resolved Hide resolved
pkg/services/authn/clients/grafana.go Show resolved Hide resolved
pkg/services/user/model.go Outdated Show resolved Hide resolved
pkg/services/sqlstore/permissions/dashboard.go Outdated Show resolved Hide resolved
pkg/services/sqlstore/permissions/dashboard.go Outdated Show resolved Hide resolved
Copy link
Contributor

@gamab gamab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍
I left a couple of comments/suggestions, the cyclomatic complexity of the buildClause function is what worries me most. I'd consider splitting it 🤔

mgyongyosi and others added 3 commits July 11, 2023 14:43
Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
Copy link
Contributor

@IevaVasiljeva IevaVasiljeva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, looks good to me now! The permission filter clause function is pretty mahoosive now, so I think it would be good to try to refactor it. But let's leave that for a separate PR.

@@ -184,7 +184,7 @@ func (s *UserSync) upsertAuthConnection(ctx context.Context, userID int64, ident
if createConnection {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @mgyongyosi will this change start to create user_auth entries for basic auth and api key auth? This might be problematic but not an active issue.

Most important would be for sessions to not set auth modules as they should take the 'last authenticated' method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, because the SyncHook is only executed when the ClientParams.SyncUser is set to true and it's set to true for only external auth.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for me, thanks for the confirmation

Copy link
Contributor

@Jguer Jguer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IevaVasiljeva
Copy link
Contributor

Haha, I realised that my comment pretty much repeated what @gamab said 🙈 But it looks like we all agree that this could use improvement. I've made an issue for it, so that we don't forget.

@mgyongyosi mgyongyosi merged commit 5efc338 into main Jul 12, 2023
11 checks passed
@mgyongyosi mgyongyosi deleted the mgyongyosi/sql-filters-empty-role branch July 12, 2023 10:31
polibb pushed a commit that referenced this pull request Jul 14, 2023
…0749)

* Search sql filter draft, unfinished

* Search works for empty roles

* Add current AuthModule to SignedInUser

* clean up, changes to the search

* Use constant prefixes

* Change AuthModule to AuthenticatedBy

* Add tests for using the permissions from the SignedInUser

* Refactor and simplify code

* Fix sql generation for pg and mysql

* Fixes, clean up

* Add test for empty permission list

* Fix

* Fix any vs all in case of edit permission

* Update pkg/services/authn/authn.go

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* Update pkg/services/sqlstore/permissions/dashboard_test.go

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* Fixes, changes based on the review

---------

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
@ricky-undeadcoders ricky-undeadcoders modified the milestones: 10.1.x, 10.1.0 Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
add to changelog area/auth/rbac Grafana role-based access control area/backend no-backport Skip backport of PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants