From 2db2bdcfd2c1dd413b21c921358506c647c6d32c Mon Sep 17 00:00:00 2001 From: jguer Date: Wed, 14 Sep 2022 18:16:18 +0200 Subject: [PATCH 1/5] Auth: Allow admins to change oauth user info it it's not synced. Co-authored-by: Gabriel MABILLE --- .betterer.results | 2 +- packages/grafana-data/src/types/config.ts | 5 +++++ packages/grafana-runtime/src/config.ts | 4 +++- pkg/api/frontendsettings.go | 2 ++ public/app/features/admin/UserAdminPage.tsx | 10 +++++++++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.betterer.results b/.betterer.results index 6798e873c5917bc..56a18263662a4fc 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1028,7 +1028,7 @@ exports[`better eslint`] = { ], "packages/grafana-runtime/src/config.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], + [0, 0, 0, "Do not use any type assertions.", "1"], [0, 0, 0, "Do not use any type assertions.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 8c87464f4946b40..0045f0376c984c4 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -153,6 +153,7 @@ export interface GrafanaConfig { isPublicDashboardView: boolean; datasources: { [str: string]: DataSourceInstanceSettings }; panels: { [key: string]: PanelPluginMeta }; + auth: AuthSettings; minRefreshInterval: string; appSubUrl: string; windowTitlePrefix: string; @@ -214,3 +215,7 @@ export interface GrafanaConfig { rudderstackSdkUrl: string | undefined; rudderstackConfigUrl: string | undefined; } + +export interface AuthSettings { + OAuthSkipOrgRoleUpdateSync?: boolean; +} diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 28e6566460101e1..5384eb7a3c0783d 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -1,6 +1,7 @@ import { merge } from 'lodash'; import { + AuthSettings, BootData, BuildInfo, createTheme, @@ -27,6 +28,7 @@ export class GrafanaBootConfig implements GrafanaConfig { isPublicDashboardView: boolean; datasources: { [str: string]: DataSourceInstanceSettings } = {}; panels: { [key: string]: PanelPluginMeta } = {}; + auth: AuthSettings = {} as AuthSettings; minRefreshInterval = ''; appUrl = ''; appSubUrl = ''; @@ -107,7 +109,7 @@ export class GrafanaBootConfig implements GrafanaConfig { pluginAdminExternalManageEnabled = false; pluginCatalogHiddenPlugins: string[] = []; expressionsEnabled = false; - customTheme?: any; + customTheme?: undefined; awsAllowedAuthProviders: string[] = []; awsAssumeRoleEnabled = false; azure: AzureSettings = { diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 2b73df3b5f30d60..84e88f2478f6b2f 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -136,6 +136,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i "editorsCanAdmin": hs.Cfg.EditorsCanAdmin, "disableSanitizeHtml": hs.Cfg.DisableSanitizeHtml, "pluginsToPreload": pluginsToPreload, + "auth": map[string]interface{}{ + "OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync}, "buildInfo": map[string]interface{}{ "hideVersion": hideVersion, "version": version, diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index 502855c27c1bba6..b7e7193824974f4 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -4,6 +4,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { NavModelItem } from '@grafana/data'; import { featureEnabled } from '@grafana/runtime'; import { Page } from 'app/core/components/Page/Page'; +import config from 'app/core/config'; import { contextSrv } from 'app/core/core'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { StoreState, UserDTO, UserOrg, UserSession, SyncInfo, UserAdminError, AccessControlAction } from 'app/types'; @@ -38,6 +39,8 @@ interface OwnProps extends GrafanaRouteComponentProps<{ id: string }> { error?: UserAdminError; } +const SyncedOAuthLabels: string[] = ['GitHub', 'GitLab', 'AzureAD', 'OAuth']; + export class UserAdminPage extends PureComponent { async componentDidMount() { const { match, loadAdminUserPage } = this.props; @@ -105,6 +108,11 @@ export class UserAdminPage extends PureComponent { const isLDAPUser = user && user.isExternal && user.authLabels && user.authLabels.includes('LDAP'); const canReadSessions = contextSrv.hasPermission(AccessControlAction.UsersAuthTokenList); const canReadLDAPStatus = contextSrv.hasPermission(AccessControlAction.LDAPStatusRead); + const isOAuthUserWithSkippableSync = + user && user.isExternal && user.authLabels && user.authLabels.some((r) => SyncedOAuthLabels.includes(r)); + const isUserSynced = + (user?.isExternal && !isOAuthUserWithSkippableSync) || + (!config.auth.OAuthSkipOrgRoleUpdateSync && isOAuthUserWithSkippableSync); const pageNav: NavModelItem = { text: user?.login ?? '', @@ -137,7 +145,7 @@ export class UserAdminPage extends PureComponent { Date: Thu, 15 Sep 2022 14:04:47 +0200 Subject: [PATCH 2/5] Update public/app/features/admin/UserAdminPage.tsx Co-authored-by: Alex Khomenko --- public/app/features/admin/UserAdminPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index b7e7193824974f4..cf1112369397a5b 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -109,7 +109,7 @@ export class UserAdminPage extends PureComponent { const canReadSessions = contextSrv.hasPermission(AccessControlAction.UsersAuthTokenList); const canReadLDAPStatus = contextSrv.hasPermission(AccessControlAction.LDAPStatusRead); const isOAuthUserWithSkippableSync = - user && user.isExternal && user.authLabels && user.authLabels.some((r) => SyncedOAuthLabels.includes(r)); + user?.isExternal && user?.authLabels?.some((r) => SyncedOAuthLabels.includes(r)); const isUserSynced = (user?.isExternal && !isOAuthUserWithSkippableSync) || (!config.auth.OAuthSkipOrgRoleUpdateSync && isOAuthUserWithSkippableSync); From 66c0a6fcadc5caf4a7fc665234f515734b64a99f Mon Sep 17 00:00:00 2001 From: gamab Date: Thu, 15 Sep 2022 14:34:55 +0200 Subject: [PATCH 3/5] Add missing import --- packages/grafana-data/src/types/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grafana-data/src/types/index.ts b/packages/grafana-data/src/types/index.ts index 0f0c2472485196a..0c55fe6879e85af 100644 --- a/packages/grafana-data/src/types/index.ts +++ b/packages/grafana-data/src/types/index.ts @@ -41,6 +41,7 @@ export type { BootData, OAuth, OAuthSettings, + AuthSettings, GrafanaConfig, BuildInfo, LicenseInfo, From f9535f6abca50922fa7ba72b06f6cb68261267ab Mon Sep 17 00:00:00 2001 From: gamab Date: Thu, 15 Sep 2022 14:38:02 +0200 Subject: [PATCH 4/5] Simplify init Co-authored-by: Josh Hunt Co-authored-by: Alex Khomenko --- .betterer.results | 3 +-- packages/grafana-runtime/src/config.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.betterer.results b/.betterer.results index 56a18263662a4fc..f0c43b2adebaa95 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1029,8 +1029,7 @@ exports[`better eslint`] = { "packages/grafana-runtime/src/config.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Do not use any type assertions.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"] + [0, 0, 0, "Unexpected any. Specify a different type.", "2"] ], "packages/grafana-runtime/src/services/AngularLoader.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 5384eb7a3c0783d..b0b84b51e324129 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -28,7 +28,7 @@ export class GrafanaBootConfig implements GrafanaConfig { isPublicDashboardView: boolean; datasources: { [str: string]: DataSourceInstanceSettings } = {}; panels: { [key: string]: PanelPluginMeta } = {}; - auth: AuthSettings = {} as AuthSettings; + auth: AuthSettings = {}; minRefreshInterval = ''; appUrl = ''; appSubUrl = ''; From 1b62507fa44a50c8f2f39aa058c66d67ff97e290 Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Thu, 15 Sep 2022 17:33:16 +0200 Subject: [PATCH 5/5] SAML: Add option to skip org role sync (#55230) * SAML: Add option to skip org role sync * Modify frontend accordingly * Remove update from config option name Co-authored-by: Jguer * Remove update from config option name Co-authored-by: Jguer * Fix typo Co-authored-by: Jguer --- .../configure-authentication/saml.md | 13 ++++++++++++- packages/grafana-data/src/types/config.ts | 1 + pkg/api/frontendsettings.go | 4 +++- public/app/features/admin/UserAdminPage.tsx | 6 ++++-- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml.md index de1a16940200f4b..ad2f0efe675b4c1 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml.md @@ -297,7 +297,7 @@ For more information about roles and permissions in Grafana, refer to [Roles and Example configuration: -```bash +```ini [auth.saml] assertion_attribute_role = role role_values_editor = editor, developer @@ -307,6 +307,17 @@ role_values_grafana_admin = superadmin **Important**: When role sync is configured, any changes of user roles and organization membership made manually in Grafana will be overwritten on next user login. Assign user organizations and roles in the IdP instead. +> **Note:** Available in Grafana version 9.2 and later. + +If you don't want user organizations and roles to be synchronized with the IdP, you can use the `skip_org_role_sync` configuration option. + +Example configuration: + +```ini +[auth.saml] +skip_org_role_sync = true +``` + ### Configure organization mapping > **Note:** Available in Grafana version 7.0 and later. diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index 0045f0376c984c4..a8b50fbbec93c5e 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -218,4 +218,5 @@ export interface GrafanaConfig { export interface AuthSettings { OAuthSkipOrgRoleUpdateSync?: boolean; + SAMLSkipOrgRoleSync?: boolean; } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 84e88f2478f6b2f..05062a600bebf21 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -137,7 +137,9 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i "disableSanitizeHtml": hs.Cfg.DisableSanitizeHtml, "pluginsToPreload": pluginsToPreload, "auth": map[string]interface{}{ - "OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync}, + "OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync, + "SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false), + }, "buildInfo": map[string]interface{}{ "hideVersion": hideVersion, "version": version, diff --git a/public/app/features/admin/UserAdminPage.tsx b/public/app/features/admin/UserAdminPage.tsx index cf1112369397a5b..879dda5e114a58c 100644 --- a/public/app/features/admin/UserAdminPage.tsx +++ b/public/app/features/admin/UserAdminPage.tsx @@ -110,9 +110,11 @@ export class UserAdminPage extends PureComponent { const canReadLDAPStatus = contextSrv.hasPermission(AccessControlAction.LDAPStatusRead); const isOAuthUserWithSkippableSync = user?.isExternal && user?.authLabels?.some((r) => SyncedOAuthLabels.includes(r)); + const isSAMLUser = user?.isExternal && user?.authLabels?.includes('SAML'); const isUserSynced = - (user?.isExternal && !isOAuthUserWithSkippableSync) || - (!config.auth.OAuthSkipOrgRoleUpdateSync && isOAuthUserWithSkippableSync); + (user?.isExternal && !(isOAuthUserWithSkippableSync || isSAMLUser)) || + (!config.auth.OAuthSkipOrgRoleUpdateSync && isOAuthUserWithSkippableSync) || + (!config.auth.SAMLSkipOrgRoleSync && isSAMLUser); const pageNav: NavModelItem = { text: user?.login ?? '',