From 3e2e9f93b96ad565607a30387cd230ef87a4e985 Mon Sep 17 00:00:00 2001 From: Jo Date: Thu, 15 Sep 2022 18:06:09 +0200 Subject: [PATCH] Auth: Allow admins to manually change oauth user role if `oauth_skip_org_role_update_sync` is enabled (#55182) * Auth: Allow admins to change oauth user info it it's not synced. Co-authored-by: Gabriel MABILLE * Update public/app/features/admin/UserAdminPage.tsx Co-authored-by: Alex Khomenko * Add missing import * Simplify init Co-authored-by: Josh Hunt Co-authored-by: Alex Khomenko * 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 Co-authored-by: Gabriel MABILLE Co-authored-by: Alex Khomenko Co-authored-by: gamab Co-authored-by: Josh Hunt --- .betterer.results | 5 ++--- .../configure-authentication/saml.md | 13 ++++++++++++- packages/grafana-data/src/types/config.ts | 6 ++++++ packages/grafana-data/src/types/index.ts | 1 + packages/grafana-runtime/src/config.ts | 4 +++- pkg/api/frontendsettings.go | 4 ++++ public/app/features/admin/UserAdminPage.tsx | 12 +++++++++++- 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/.betterer.results b/.betterer.results index 658624103d9..a2718115ff9 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1028,9 +1028,8 @@ 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.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"] + [0, 0, 0, "Do not use any type assertions.", "1"], + [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/docs/sources/setup-grafana/configure-security/configure-authentication/saml.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml.md index de1a1694020..ad2f0efe675 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 8c87464f494..a8b50fbbec9 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,8 @@ export interface GrafanaConfig { rudderstackSdkUrl: string | undefined; rudderstackConfigUrl: string | undefined; } + +export interface AuthSettings { + OAuthSkipOrgRoleUpdateSync?: boolean; + SAMLSkipOrgRoleSync?: boolean; +} diff --git a/packages/grafana-data/src/types/index.ts b/packages/grafana-data/src/types/index.ts index 0f0c2472485..0c55fe6879e 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, diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 28e65664601..b0b84b51e32 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 = {}; 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 2b73df3b5f3..05062a600be 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -136,6 +136,10 @@ 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, + "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 502855c27c1..879dda5e114 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,13 @@ 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?.isExternal && user?.authLabels?.some((r) => SyncedOAuthLabels.includes(r)); + const isSAMLUser = user?.isExternal && user?.authLabels?.includes('SAML'); + const isUserSynced = + (user?.isExternal && !(isOAuthUserWithSkippableSync || isSAMLUser)) || + (!config.auth.OAuthSkipOrgRoleUpdateSync && isOAuthUserWithSkippableSync) || + (!config.auth.SAMLSkipOrgRoleSync && isSAMLUser); const pageNav: NavModelItem = { text: user?.login ?? '', @@ -137,7 +147,7 @@ export class UserAdminPage extends PureComponent {