From 648aa62264758dad815e1b47a7356b42e7841717 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 9 Jul 2019 20:33:56 +0200 Subject: [PATCH 1/9] grafana/toolkit: Copy or extract static files (#18006) * Replace webpack ng annotate plugin with babel-plugin-angularjs-annotate * Copy statics(png/svg) when necessary or keep the original path when files loaded via file-loader * Update readme --- packages/grafana-toolkit/README.md | 4 +++- packages/grafana-toolkit/src/config/webpack.plugin.config.ts | 4 ++-- packages/grafana-toolkit/src/config/webpack/loaders.ts | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/grafana-toolkit/README.md b/packages/grafana-toolkit/README.md index 71ff7376ac9..58b94d101c1 100644 --- a/packages/grafana-toolkit/README.md +++ b/packages/grafana-toolkit/README.md @@ -84,7 +84,7 @@ Adidtionaly, you can also provide additional Jest config via package.json file. ## Working with CSS & static assets -We support pure css, SASS and CSS in JS approach (via Emotion). All static assets referenced in your code (i.e. images) should be placed under `src/static` directory and referenced using relative paths. +We support pure css, SASS and CSS in JS approach (via Emotion). 1. Single css/sass file Create your css/sass file and import it in your plugin entry point (typically module.ts): @@ -101,6 +101,8 @@ If you want to provide different stylesheets for dark/light theme, create `dark. TODO: add note about loadPluginCss +Note that static files (png, svg, json, html) are all copied to dist directory when the plugin is bundled. Relative paths to those files does not change. + 3. Emotion Starting from Grafana 6.2 our suggested way of styling plugins is by using [Emotion](https://emotion.sh). It's a css-in-js library that we use internaly at Grafana. The biggest advantage of using Emotion is that you will get access to Grafana Theme variables. diff --git a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts index 3b640df789d..8d1c6bde235 100644 --- a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts +++ b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts @@ -83,8 +83,8 @@ const getCommonPlugins = (options: WebpackConfigurationOptions) => { { from: '../LICENSE', to: '.' }, { from: 'img/*', to: '.' }, { from: '**/*.json', to: '.' }, - // { from: '**/*.svg', to: '.' }, - // { from: '**/*.png', to: '.' }, + { from: '**/*.svg', to: '.' }, + { from: '**/*.png', to: '.' }, { from: '**/*.html', to: '.' }, ], { logLevel: options.watch ? 'silent' : 'warn' } diff --git a/packages/grafana-toolkit/src/config/webpack/loaders.ts b/packages/grafana-toolkit/src/config/webpack/loaders.ts index 3cdcfeee486..30941f7972e 100644 --- a/packages/grafana-toolkit/src/config/webpack/loaders.ts +++ b/packages/grafana-toolkit/src/config/webpack/loaders.ts @@ -122,8 +122,8 @@ export const getFileLoaders = () => { ? { loader: 'file-loader', options: { - outputPath: 'static', - name: '[name].[hash:8].[ext]', + outputPath: '/', + name: '[path][name].[ext]', }, } : // When using single css import images are inlined as base64 URIs in the result bundle From 81ff856568d36bb6c8a9aae95fcf9ef585c1ed93 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 10 Jul 2019 00:40:33 -0700 Subject: [PATCH 2/9] grafana-cli: allow installing plugins from a local zip file (#18021) --- .../grafana-cli/commands/install_command.go | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index db390768263..ad77c39ba87 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -85,7 +85,7 @@ func InstallPlugin(pluginName, version string, c utils.CommandLine) error { } logger.Infof("installing %v @ %v\n", pluginName, version) - logger.Infof("from url: %v\n", downloadURL) + logger.Infof("from: %v\n", downloadURL) logger.Infof("into: %v\n", pluginFolder) logger.Info("\n") @@ -145,18 +145,27 @@ func downloadFile(pluginName, filePath, url string) (err error) { } }() - resp, err := http.Get(url) // #nosec - if err != nil { - return err - } - defer resp.Body.Close() + var bytes []byte - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err + if _, err := os.Stat(url); err == nil { + bytes, err = ioutil.ReadFile(url) + if err != nil { + return err + } + } else { + resp, err := http.Get(url) // #nosec + if err != nil { + return err + } + defer resp.Body.Close() + + bytes, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } } - return extractFiles(body, pluginName, filePath) + return extractFiles(bytes, pluginName, filePath) } func extractFiles(body []byte, pluginName string, filePath string) error { From ebff8830169e3fa2602147988ff08bccaa7d393b Mon Sep 17 00:00:00 2001 From: Damien Lespiau Date: Wed, 10 Jul 2019 10:03:06 +0100 Subject: [PATCH 3/9] Loki: Don't use _ numerical separator (#18016) It breaks the build on a fresh checkout and install. Fixes: #18015 --- public/app/plugins/datasource/loki/language_provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index f465d9c022a..8704e03b420 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -22,7 +22,7 @@ const DEFAULT_KEYS = ['job', 'namespace']; const EMPTY_SELECTOR = '{}'; const HISTORY_ITEM_COUNT = 10; const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h -const NS_IN_MS = 1_000_000; +const NS_IN_MS = 1000000; export const LABEL_REFRESH_INTERVAL = 1000 * 30; // 30sec const wrapLabel = (label: string) => ({ label }); From 5f0a7f43c33d66479eba5a8d3771a9789d7fb797 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 10 Jul 2019 12:06:51 +0300 Subject: [PATCH 4/9] Users: show badges for each auth provider (#17869) * Users: show badges for each auth provider * Chore: don't use functions in angular bindings * Users: minor style changes to labels * Chore: convert auth labels on the backed side, deduplicate frontend code * Users: use authLabels everywhere instead of authModule * User: fix edit user page style * Users: minor fixes after review --- pkg/api/team_members.go | 17 ----- pkg/api/user.go | 28 +++++++- pkg/models/user.go | 6 +- .../app/features/admin/AdminEditUserCtrl.ts | 2 +- .../app/features/admin/AdminListUsersCtrl.ts | 30 ++++++-- .../features/admin/partials/edit_user.html | 72 ++++++++++--------- public/app/features/admin/partials/users.html | 4 +- 7 files changed, 99 insertions(+), 60 deletions(-) diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go index 5d34323c55f..0d8139e4e5c 100644 --- a/pkg/api/team_members.go +++ b/pkg/api/team_members.go @@ -30,23 +30,6 @@ func GetTeamMembers(c *m.ReqContext) Response { return JSON(200, query.Result) } -func GetAuthProviderLabel(authModule string) string { - switch authModule { - case "oauth_github": - return "GitHub" - case "oauth_google": - return "Google" - case "oauth_gitlab": - return "GitLab" - case "oauth_grafana_com", "oauth_grafananet": - return "grafana.com" - case "ldap", "": - return "LDAP" - default: - return "OAuth" - } -} - // POST /api/teams/:teamId/members func (hs *HTTPServer) AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response { cmd.OrgId = c.OrgId diff --git a/pkg/api/user.go b/pkg/api/user.go index 2d82b60ba33..528e7431ffd 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -29,8 +29,11 @@ func getUserUserProfile(userID int64) Response { } getAuthQuery := m.GetAuthInfoQuery{UserId: userID} + query.Result.AuthLabels = []string{} if err := bus.Dispatch(&getAuthQuery); err == nil { - query.Result.AuthModule = []string{getAuthQuery.Result.AuthModule} + authLabel := GetAuthProviderLabel(getAuthQuery.Result.AuthModule) + query.Result.AuthLabels = append(query.Result.AuthLabels, authLabel) + query.Result.IsExternal = true } return JSON(200, query.Result) @@ -277,6 +280,12 @@ func searchUser(c *m.ReqContext) (*m.SearchUsersQuery, error) { for _, user := range query.Result.Users { user.AvatarUrl = dtos.GetGravatarUrl(user.Email) + user.AuthLabels = make([]string, 0) + if user.AuthModule != nil && len(user.AuthModule) > 0 { + for _, authModule := range user.AuthModule { + user.AuthLabels = append(user.AuthLabels, GetAuthProviderLabel(authModule)) + } + } } query.Result.Page = page @@ -315,3 +324,20 @@ func ClearHelpFlags(c *m.ReqContext) Response { return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) } + +func GetAuthProviderLabel(authModule string) string { + switch authModule { + case "oauth_github": + return "GitHub" + case "oauth_google": + return "Google" + case "oauth_gitlab": + return "GitLab" + case "oauth_grafana_com", "oauth_grafananet": + return "grafana.com" + case "ldap", "": + return "LDAP" + default: + return "OAuth" + } +} diff --git a/pkg/models/user.go b/pkg/models/user.go index a9032f1a8d8..bf52aed6160 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -216,7 +216,8 @@ type UserProfileDTO struct { OrgId int64 `json:"orgId"` IsGrafanaAdmin bool `json:"isGrafanaAdmin"` IsDisabled bool `json:"isDisabled"` - AuthModule []string `json:"authModule"` + IsExternal bool `json:"isExternal"` + AuthLabels []string `json:"authLabels"` } type UserSearchHitDTO struct { @@ -229,7 +230,8 @@ type UserSearchHitDTO struct { IsDisabled bool `json:"isDisabled"` LastSeenAt time.Time `json:"lastSeenAt"` LastSeenAtAge string `json:"lastSeenAtAge"` - AuthModule AuthModuleConversion `json:"authModule"` + AuthLabels []string `json:"authLabels"` + AuthModule AuthModuleConversion `json:"-"` } type UserIdDTO struct { diff --git a/public/app/features/admin/AdminEditUserCtrl.ts b/public/app/features/admin/AdminEditUserCtrl.ts index 8eba14fbcdc..1816a6dfb1d 100644 --- a/public/app/features/admin/AdminEditUserCtrl.ts +++ b/public/app/features/admin/AdminEditUserCtrl.ts @@ -179,7 +179,7 @@ export default class AdminEditUserCtrl { const user = $scope.user; // External user can not be disabled - if (user.authModule) { + if (user.isExternal) { event.preventDefault(); event.stopPropagation(); return; diff --git a/public/app/features/admin/AdminListUsersCtrl.ts b/public/app/features/admin/AdminListUsersCtrl.ts index 3f1e1e1bf51..3e4ba2e37a3 100644 --- a/public/app/features/admin/AdminListUsersCtrl.ts +++ b/public/app/features/admin/AdminListUsersCtrl.ts @@ -1,5 +1,6 @@ import { BackendSrv } from 'app/core/services/backend_srv'; import { NavModelSrv } from 'app/core/core'; +import tags from 'app/core/utils/tags'; export default class AdminListUsersCtrl { users: any; @@ -32,6 +33,8 @@ export default class AdminListUsersCtrl { for (let i = 1; i < this.totalPages + 1; i++) { this.pages.push({ page: i, current: i === this.page }); } + + this.addUsersAuthLabels(); }); } @@ -40,10 +43,29 @@ export default class AdminListUsersCtrl { this.getUsers(); } - getAuthModule(user: any) { - if (user.authModule && user.authModule.length) { - return user.authModule[0]; + addUsersAuthLabels() { + for (const user of this.users) { + user.authLabel = getAuthLabel(user); + user.authLabelStyle = getAuthLabelStyle(user.authLabel); } - return undefined; } } + +function getAuthLabel(user: any) { + if (user.authLabels && user.authLabels.length) { + return user.authLabels[0]; + } + return ''; +} + +function getAuthLabelStyle(label: string) { + if (label === 'LDAP' || !label) { + return {}; + } + + const { color, borderColor } = tags.getTagColorsFromName(label); + return { + 'background-color': color, + 'border-color': borderColor, + }; +} diff --git a/public/app/features/admin/partials/edit_user.html b/public/app/features/admin/partials/edit_user.html index 12bfbe8f722..7182f123149 100644 --- a/public/app/features/admin/partials/edit_user.html +++ b/public/app/features/admin/partials/edit_user.html @@ -118,48 +118,52 @@

Sessions

- - - - - - - - - - - - - - - - - - - - -
Last seenLogged onIP addressBrowser & OS
Now{{session.seenAt}}{{session.createdAt}}{{session.clientIp}}{{session.browser}} on {{session.os}} {{session.osVersion}} - -
+
+ + + + + + + + + + + + + + + + + + + + +
Last seenLogged onIP addressBrowser & OS
Now{{session.seenAt}}{{session.createdAt}}{{session.clientIp}}{{session.browser}} on {{session.os}} {{session.osVersion}} + +
+
+
+ +
- + +

User status

-

User status

-
@@ -168,8 +172,8 @@ class="btn btn-primary" ng-if="user.isDisabled" ng-click="disableUser($event)" - bs-tooltip="user.authModule ? 'External user cannot be activated or deactivated' : ''" - ng-class="{'disabled': user.authModule}" + bs-tooltip="user.isExternal ? 'External user cannot be enabled or disabled' : ''" + ng-class="{'disabled': user.isExternal}" > Enable diff --git a/public/app/features/admin/partials/users.html b/public/app/features/admin/partials/users.html index da33deeef0c..21346b92aa7 100644 --- a/public/app/features/admin/partials/users.html +++ b/public/app/features/admin/partials/users.html @@ -55,7 +55,9 @@ - LDAP + + {{user.authLabel}} + Disabled From 5d3a60d46eb8cfc494c0fe9bb9ff64601a3ce485 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Wed, 10 Jul 2019 12:25:21 +0200 Subject: [PATCH 5/9] LDAP: Adds bind before searching LDAP for non-login cases. (#18023) --- pkg/services/ldap/ldap.go | 36 +++++++++++++++++++------- pkg/services/ldap/ldap_login_test.go | 2 +- pkg/services/ldap/ldap_private_test.go | 4 +-- pkg/services/ldap/ldap_test.go | 6 ++--- pkg/services/multildap/multildap.go | 8 ++++++ pkg/services/multildap/testing.go | 12 +++++++-- 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/pkg/services/ldap/ldap.go b/pkg/services/ldap/ldap.go index c35f274b7e3..d93fdbdabcb 100644 --- a/pkg/services/ldap/ldap.go +++ b/pkg/services/ldap/ldap.go @@ -31,7 +31,8 @@ type IConnection interface { type IServer interface { Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error) Users([]string) ([]*models.ExternalUserInfo, error) - Auth(string, string) error + Bind() error + UserBind(string, string) error Dial() error Close() } @@ -43,6 +44,23 @@ type Server struct { log log.Logger } +// Bind authenticates the connection with the LDAP server +// - with the username and password setup in the config +// - or, anonymously +func (server *Server) Bind() error { + if server.shouldAuthAdmin() { + if err := server.AuthAdmin(); err != nil { + return err + } + } else { + err := server.Connection.UnauthenticatedBind(server.Config.BindDN) + if err != nil { + return err + } + } + return nil +} + // UsersMaxRequest is a max amount of users we can request via Users(). // Since many LDAP servers has limitations // on how much items can we return in one request @@ -149,7 +167,7 @@ func (server *Server) Login(query *models.LoginUserQuery) ( } } else if server.shouldSingleBind() { authAndBind = true - err = server.Auth(server.singleBindDN(query.Username), query.Password) + err = server.UserBind(server.singleBindDN(query.Username), query.Password) if err != nil { return nil, err } @@ -179,7 +197,7 @@ func (server *Server) Login(query *models.LoginUserQuery) ( if !authAndBind { // Authenticate user - err = server.Auth(user.AuthId, query.Password) + err = server.UserBind(user.AuthId, query.Password) if err != nil { return nil, err } @@ -380,9 +398,9 @@ func (server *Server) shouldAuthAdmin() bool { return server.Config.BindPassword != "" } -// Auth authentificates user in LDAP -func (server *Server) Auth(username, password string) error { - err := server.auth(username, password) +// UserBind authenticates the connection with the LDAP server +func (server *Server) UserBind(username, password string) error { + err := server.userBind(username, password) if err != nil { server.log.Error( fmt.Sprintf("Cannot authentificate user %s in LDAP", username), @@ -397,7 +415,7 @@ func (server *Server) Auth(username, password string) error { // AuthAdmin authentificates LDAP admin user func (server *Server) AuthAdmin() error { - err := server.auth(server.Config.BindDN, server.Config.BindPassword) + err := server.userBind(server.Config.BindDN, server.Config.BindPassword) if err != nil { server.log.Error( "Cannot authentificate admin user in LDAP", @@ -410,8 +428,8 @@ func (server *Server) AuthAdmin() error { return nil } -// auth is helper for several types of LDAP authentification -func (server *Server) auth(path, password string) error { +// userBind authenticates the connection with the LDAP server +func (server *Server) userBind(path, password string) error { err := server.Connection.Bind(path, password) if err != nil { if ldapErr, ok := err.(*ldap.Error); ok { diff --git a/pkg/services/ldap/ldap_login_test.go b/pkg/services/ldap/ldap_login_test.go index 6aee77e4863..abdf6c2c3a4 100644 --- a/pkg/services/ldap/ldap_login_test.go +++ b/pkg/services/ldap/ldap_login_test.go @@ -19,7 +19,7 @@ func TestLDAPLogin(t *testing.T) { } Convey("Login()", t, func() { - Convey("Should get invalid credentials when auth fails", func() { + Convey("Should get invalid credentials when userBind fails", func() { connection := &MockConnection{} entry := ldap.Entry{} result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}} diff --git a/pkg/services/ldap/ldap_private_test.go b/pkg/services/ldap/ldap_private_test.go index f1ec97c176b..1a43087399f 100644 --- a/pkg/services/ldap/ldap_private_test.go +++ b/pkg/services/ldap/ldap_private_test.go @@ -145,7 +145,7 @@ func TestLDAPPrivateMethods(t *testing.T) { }) Convey("shouldAuthAdmin()", t, func() { - Convey("it should require admin auth", func() { + Convey("it should require admin userBind", func() { server := &Server{ Config: &ServerConfig{ BindPassword: "test", @@ -156,7 +156,7 @@ func TestLDAPPrivateMethods(t *testing.T) { So(result, ShouldBeTrue) }) - Convey("it should not require admin auth", func() { + Convey("it should not require admin userBind", func() { server := &Server{ Config: &ServerConfig{ BindPassword: "", diff --git a/pkg/services/ldap/ldap_test.go b/pkg/services/ldap/ldap_test.go index 8207e0d3187..5a87d89bdaa 100644 --- a/pkg/services/ldap/ldap_test.go +++ b/pkg/services/ldap/ldap_test.go @@ -102,7 +102,7 @@ func TestPublicAPI(t *testing.T) { }) }) - Convey("Auth()", t, func() { + Convey("UserBind()", t, func() { Convey("Should use provided DN and password", func() { connection := &MockConnection{} var actualUsername, actualPassword string @@ -119,7 +119,7 @@ func TestPublicAPI(t *testing.T) { } dn := "cn=user,ou=users,dc=grafana,dc=org" - err := server.Auth(dn, "pwd") + err := server.UserBind(dn, "pwd") So(err, ShouldBeNil) So(actualUsername, ShouldEqual, dn) @@ -141,7 +141,7 @@ func TestPublicAPI(t *testing.T) { }, log: log.New("test-logger"), } - err := server.Auth("user", "pwd") + err := server.UserBind("user", "pwd") So(err, ShouldEqual, expected) }) }) diff --git a/pkg/services/multildap/multildap.go b/pkg/services/multildap/multildap.go index 742cf3c3942..d2560d07e93 100644 --- a/pkg/services/multildap/multildap.go +++ b/pkg/services/multildap/multildap.go @@ -109,6 +109,10 @@ func (multiples *MultiLDAP) User(login string) ( defer server.Close() + if err := server.Bind(); err != nil { + return nil, err + } + users, err := server.Users(search) if err != nil { return nil, err @@ -142,6 +146,10 @@ func (multiples *MultiLDAP) Users(logins []string) ( defer server.Close() + if err := server.Bind(); err != nil { + return nil, err + } + users, err := server.Users(logins) if err != nil { return nil, err diff --git a/pkg/services/multildap/testing.go b/pkg/services/multildap/testing.go index a5d5caefadd..9d3623ad8d3 100644 --- a/pkg/services/multildap/testing.go +++ b/pkg/services/multildap/testing.go @@ -11,12 +11,15 @@ type MockLDAP struct { loginCalledTimes int closeCalledTimes int usersCalledTimes int + bindCalledTimes int dialErrReturn error loginErrReturn error loginReturn *models.ExternalUserInfo + bindErrReturn error + usersErrReturn error usersFirstReturn []*models.ExternalUserInfo usersRestReturn []*models.ExternalUserInfo @@ -40,8 +43,8 @@ func (mock *MockLDAP) Users([]string) ([]*models.ExternalUserInfo, error) { return mock.usersRestReturn, mock.usersErrReturn } -// Auth test fn -func (mock *MockLDAP) Auth(string, string) error { +// UserBind test fn +func (mock *MockLDAP) UserBind(string, string) error { return nil } @@ -56,6 +59,11 @@ func (mock *MockLDAP) Close() { mock.closeCalledTimes = mock.closeCalledTimes + 1 } +func (mock *MockLDAP) Bind() error { + mock.bindCalledTimes++ + return mock.bindErrReturn +} + // MockMultiLDAP represents testing struct for multildap testing type MockMultiLDAP struct { LoginCalledTimes int From 6a3a2f5f9448dafc5b61a038e27523001f409a24 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 10 Jul 2019 12:28:40 +0200 Subject: [PATCH 6/9] CLI: Fix encrypt-datasource-passwords fails with sql error (#18014) Now handles secure_json_data stored as null in database when running the encrypt-datasource-passwords migration. Fixes #17948 --- .../encrypt_datasource_passwords.go | 18 ++++++++----- .../encrypt_datasource_passwords_test.go | 27 ++++++++++++++++--- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go index e55fa2d70b8..26418714a0d 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords.go @@ -62,7 +62,7 @@ func EncryptDatasourcePaswords(c utils.CommandLine, sqlStore *sqlstore.SqlStore) } func migrateColumn(session *sqlstore.DBSession, column string) (int, error) { - var rows []map[string]string + var rows []map[string][]byte session.Cols("id", column, "secure_json_data") session.Table("data_source") @@ -78,7 +78,7 @@ func migrateColumn(session *sqlstore.DBSession, column string) (int, error) { return rowsUpdated, errutil.Wrapf(err, "failed to update column: %s", column) } -func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordFieldName string) (int, error) { +func updateRows(session *sqlstore.DBSession, rows []map[string][]byte, passwordFieldName string) (int, error) { var rowsUpdated int for _, row := range rows { @@ -94,7 +94,7 @@ func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordF newRow := map[string]interface{}{"secure_json_data": data, passwordFieldName: ""} session.Table("data_source") - session.Where("id = ?", row["id"]) + session.Where("id = ?", string(row["id"])) // Setting both columns while having value only for secure_json_data should clear the [passwordFieldName] column session.Cols("secure_json_data", passwordFieldName) @@ -108,16 +108,20 @@ func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordF return rowsUpdated, nil } -func getUpdatedSecureJSONData(row map[string]string, passwordFieldName string) (map[string]interface{}, error) { - encryptedPassword, err := util.Encrypt([]byte(row[passwordFieldName]), setting.SecretKey) +func getUpdatedSecureJSONData(row map[string][]byte, passwordFieldName string) (map[string]interface{}, error) { + encryptedPassword, err := util.Encrypt(row[passwordFieldName], setting.SecretKey) if err != nil { return nil, err } var secureJSONData map[string]interface{} - if err := json.Unmarshal([]byte(row["secure_json_data"]), &secureJSONData); err != nil { - return nil, err + if len(row["secure_json_data"]) > 0 { + if err := json.Unmarshal(row["secure_json_data"], &secureJSONData); err != nil { + return nil, err + } + } else { + secureJSONData = map[string]interface{}{} } jsonFieldName := util.ToCamelCase(passwordFieldName) diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go index 64987423dec..2d9a04d3775 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go @@ -20,19 +20,30 @@ func TestPasswordMigrationCommand(t *testing.T) { datasources := []*models.DataSource{ {Type: "influxdb", Name: "influxdb", Password: "foobar"}, {Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar"}, - {Type: "prometheus", Name: "prometheus", SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{})}, + {Type: "prometheus", Name: "prometheus"}, + {Type: "elasticsearch", Name: "elasticsearch", Password: "pwd"}, } // set required default values for _, ds := range datasources { ds.Created = time.Now() ds.Updated = time.Now() - ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{}) + if ds.Name == "elasticsearch" { + ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ + "key": "value", + }) + } else { + ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{}) + } } _, err := session.Insert(&datasources) assert.Nil(t, err) + // force secure_json_data to be null to verify that migration can handle that + _, err = session.Exec("update data_source set secure_json_data = null where name = 'influxdb'") + assert.Nil(t, err) + //run migration err = EncryptDatasourcePaswords(&commandstest.FakeCommandLine{}, sqlstore) assert.Nil(t, err) @@ -41,7 +52,7 @@ func TestPasswordMigrationCommand(t *testing.T) { var dss []*models.DataSource err = session.SQL("select * from data_source").Find(&dss) assert.Nil(t, err) - assert.Equal(t, len(dss), 3) + assert.Equal(t, len(dss), 4) for _, ds := range dss { sj := ds.SecureJsonData.Decrypt() @@ -63,5 +74,15 @@ func TestPasswordMigrationCommand(t *testing.T) { if ds.Name == "prometheus" { assert.Equal(t, len(sj), 0) } + + if ds.Name == "elasticsearch" { + assert.Equal(t, ds.Password, "") + key, exist := sj["key"] + assert.True(t, exist) + password, exist := sj["password"] + assert.True(t, exist) + assert.Equal(t, password, "pwd", "expected password to be moved to securejson") + assert.Equal(t, key, "value", "expected existing key to be kept intact in securejson") + } } } From d6e812958829693786095404084ebeae7140e079 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Wed, 10 Jul 2019 12:50:52 +0200 Subject: [PATCH 7/9] Packages: create shared tsconfig.json (#18010) --- packages/grafana-data/tsconfig.json | 14 +++----------- packages/grafana-runtime/tsconfig.json | 14 +++----------- .../grafana-toolkit/src/cli/tasks/changelog.ts | 1 - .../grafana-toolkit/src/cli/tasks/plugin.build.ts | 2 -- .../grafana-toolkit/src/cli/tasks/plugin.ci.ts | 1 - .../grafana-toolkit/src/cli/tasks/plugin/bundle.ts | 2 -- .../grafana-toolkit/src/cli/tasks/plugin/tests.ts | 1 - .../src/cli/tasks/searchTestDataSetup.ts | 1 - .../grafana-toolkit/src/cli/tasks/toolkit.build.ts | 2 +- .../src/config/webpack.plugin.config.ts | 3 ++- .../src/config/webpack/loaders.test.ts | 1 - .../grafana-toolkit/src/config/webpack/loaders.ts | 3 --- packages/grafana-toolkit/tsconfig.json | 8 ++------ packages/grafana-ui/tsconfig.json | 14 +++----------- packages/tsconfig.json | 13 +++++++++++++ 15 files changed, 27 insertions(+), 53 deletions(-) create mode 100644 packages/tsconfig.json diff --git a/packages/grafana-data/tsconfig.json b/packages/grafana-data/tsconfig.json index dcc4fd97436..a7d3e870c45 100644 --- a/packages/grafana-data/tsconfig.json +++ b/packages/grafana-data/tsconfig.json @@ -1,19 +1,11 @@ { - "extends": "../../tsconfig.json", + "extends": "../tsconfig.json", "include": ["src/**/*.ts", "src/**/*.tsx", "../../public/app/types/jquery/*.ts"], "exclude": ["dist", "node_modules"], "compilerOptions": { "rootDirs": ["."], - "module": "esnext", - "outDir": "compiled", - "declaration": true, - "declarationDir": "dist", - "strict": true, - "alwaysStrict": true, - "noImplicitAny": true, - "strictNullChecks": true, "typeRoots": ["./node_modules/@types", "types"], - "skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors, - "removeComments": false + "declarationDir": "dist", + "outDir": "compiled" } } diff --git a/packages/grafana-runtime/tsconfig.json b/packages/grafana-runtime/tsconfig.json index dcc4fd97436..a7d3e870c45 100644 --- a/packages/grafana-runtime/tsconfig.json +++ b/packages/grafana-runtime/tsconfig.json @@ -1,19 +1,11 @@ { - "extends": "../../tsconfig.json", + "extends": "../tsconfig.json", "include": ["src/**/*.ts", "src/**/*.tsx", "../../public/app/types/jquery/*.ts"], "exclude": ["dist", "node_modules"], "compilerOptions": { "rootDirs": ["."], - "module": "esnext", - "outDir": "compiled", - "declaration": true, - "declarationDir": "dist", - "strict": true, - "alwaysStrict": true, - "noImplicitAny": true, - "strictNullChecks": true, "typeRoots": ["./node_modules/@types", "types"], - "skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors, - "removeComments": false + "declarationDir": "dist", + "outDir": "compiled" } } diff --git a/packages/grafana-toolkit/src/cli/tasks/changelog.ts b/packages/grafana-toolkit/src/cli/tasks/changelog.ts index 004ea46603a..36649d8e718 100644 --- a/packages/grafana-toolkit/src/cli/tasks/changelog.ts +++ b/packages/grafana-toolkit/src/cli/tasks/changelog.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; // @ts-ignore import * as _ from 'lodash'; import { Task, TaskRunner } from './task'; diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts index 40b4e946e5a..0266b308a84 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.build.ts @@ -4,7 +4,6 @@ import execa = require('execa'); import path = require('path'); import fs = require('fs'); import glob = require('glob'); -import util = require('util'); import { Linter, Configuration, RuleFailure } from 'tslint'; import * as prettier from 'prettier'; @@ -17,7 +16,6 @@ interface PluginBuildOptions { export const bundlePlugin = useSpinner('Compiling...', async options => await bundleFn(options)); -const readFileAsync = util.promisify(fs.readFile); // @ts-ignore export const clean = useSpinner('Cleaning', async () => await execa('rimraf', [`${process.cwd()}/dist`])); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts index 9a5834de9dc..c9ce6db4e65 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts @@ -1,6 +1,5 @@ import { Task, TaskRunner } from './task'; import { pluginBuildRunner } from './plugin.build'; -import { useSpinner } from '../utils/useSpinner'; import { restoreCwd } from '../utils/cwd'; import { getPluginJson } from '../../config/utils/pluginValidation'; diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts index b6ba6d9bda0..a5fb1d37a74 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/bundle.ts @@ -1,5 +1,3 @@ -import path = require('path'); -import fs = require('fs'); import webpack = require('webpack'); import { getWebpackConfig } from '../../../config/webpack.plugin.config'; import formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts b/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts index eba45d6c24a..a5926b9f4b4 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin/tests.ts @@ -1,4 +1,3 @@ -import path = require('path'); import * as jestCLI from 'jest-cli'; import { useSpinner } from '../../utils/useSpinner'; import { jestConfig } from '../../../config/jest.plugin.config'; diff --git a/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts b/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts index 915f431a957..0e051dc2dff 100644 --- a/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts +++ b/packages/grafana-toolkit/src/cli/tasks/searchTestDataSetup.ts @@ -46,7 +46,6 @@ export async function getTeam(team: any): Promise { } export async function addToTeam(team: any, user: any): Promise { - const members = await client.get(`/teams/${team.id}/members`); console.log(`Adding user ${user.name} to team ${team.name}`); await client.post(`/teams/${team.id}/members`, { userId: user.id }); } diff --git a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts index 0bee4054994..f76ea28e7d4 100644 --- a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts @@ -1,6 +1,6 @@ import execa = require('execa'); import * as fs from 'fs'; -import { changeCwdToGrafanaUi, restoreCwd, changeCwdToGrafanaToolkit } from '../utils/cwd'; +import { restoreCwd, changeCwdToGrafanaToolkit } from '../utils/cwd'; import chalk from 'chalk'; import { useSpinner } from '../utils/useSpinner'; import { Task, TaskRunner } from './task'; diff --git a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts index 8d1c6bde235..5e48b2d3a66 100644 --- a/packages/grafana-toolkit/src/config/webpack.plugin.config.ts +++ b/packages/grafana-toolkit/src/config/webpack.plugin.config.ts @@ -7,7 +7,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); import * as webpack from 'webpack'; -import { hasThemeStylesheets, getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders'; +import { getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders'; interface WebpackConfigurationOptions { watch?: boolean; @@ -51,6 +51,7 @@ const getManualChunk = (id: string) => { }; } } + return null; }; const getEntries = () => { diff --git a/packages/grafana-toolkit/src/config/webpack/loaders.test.ts b/packages/grafana-toolkit/src/config/webpack/loaders.test.ts index af30c990ffe..58913d20c1a 100644 --- a/packages/grafana-toolkit/src/config/webpack/loaders.test.ts +++ b/packages/grafana-toolkit/src/config/webpack/loaders.test.ts @@ -3,7 +3,6 @@ import { getStylesheetEntries, hasThemeStylesheets } from './loaders'; describe('Loaders', () => { describe('stylesheet helpers', () => { const logSpy = jest.spyOn(console, 'log').mockImplementation(); - const errorSpy = jest.spyOn(console, 'error').mockImplementation(); afterAll(() => { logSpy.mockRestore(); diff --git a/packages/grafana-toolkit/src/config/webpack/loaders.ts b/packages/grafana-toolkit/src/config/webpack/loaders.ts index 30941f7972e..aab0846b45d 100644 --- a/packages/grafana-toolkit/src/config/webpack/loaders.ts +++ b/packages/grafana-toolkit/src/config/webpack/loaders.ts @@ -1,6 +1,3 @@ -import { getPluginJson } from '../utils/pluginValidation'; - -const path = require('path'); const fs = require('fs'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); diff --git a/packages/grafana-toolkit/tsconfig.json b/packages/grafana-toolkit/tsconfig.json index 28b734aa9cd..d7c48a886b9 100644 --- a/packages/grafana-toolkit/tsconfig.json +++ b/packages/grafana-toolkit/tsconfig.json @@ -1,17 +1,13 @@ { + "extends": "../tsconfig.json", "include": ["src/**/*.ts"], "exclude": ["dist", "node_modules"], "compilerOptions": { "module": "commonjs", "rootDirs": ["."], "outDir": "dist/src", - "strict": true, - "alwaysStrict": true, - "noImplicitAny": true, - "strictNullChecks": true, + "declaration": false, "typeRoots": ["./node_modules/@types"], - "skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors, - "removeComments": false, "esModuleInterop": true, "lib": ["es2015", "es2017.string"] } diff --git a/packages/grafana-ui/tsconfig.json b/packages/grafana-ui/tsconfig.json index 0089f89d194..d6dbfc1e0b7 100644 --- a/packages/grafana-ui/tsconfig.json +++ b/packages/grafana-ui/tsconfig.json @@ -1,19 +1,11 @@ { - "extends": "../../tsconfig.json", + "extends": "../tsconfig.json", "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["dist", "node_modules"], "compilerOptions": { "rootDirs": [".", "stories"], - "module": "esnext", - "outDir": "compiled", - "declaration": true, - "declarationDir": "dist", - "strict": true, - "alwaysStrict": true, - "noImplicitAny": true, - "strictNullChecks": true, "typeRoots": ["./node_modules/@types", "types"], - "skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors, - "removeComments": false + "declarationDir": "dist", + "outDir": "compiled" } } diff --git a/packages/tsconfig.json b/packages/tsconfig.json new file mode 100644 index 00000000000..a82659b1e37 --- /dev/null +++ b/packages/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "esnext", + "declaration": true, + "strict": true, + "alwaysStrict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors, + "removeComments": false + } +} From 6649c5d75b244cea6056ad457d38936f9d652672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 10 Jul 2019 13:40:32 +0200 Subject: [PATCH 8/9] Docs: First draft of whats new in 6.3 (#17962) * Docs: First draft of whats new in 6.3 * Docs: Updated whats new article * Docs: typos * docs: fix broken link, add links and update docs index * Docs: whats new in enterprise --- docs/sources/_index.md | 6 +- docs/sources/auth/github.md | 15 +++ docs/sources/guides/whats-new-in-v6-3.md | 144 +++++++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 docs/sources/guides/whats-new-in-v6-3.md diff --git a/docs/sources/_index.md b/docs/sources/_index.md index ee0e2d1a0cf..999cb817612 100644 --- a/docs/sources/_index.md +++ b/docs/sources/_index.md @@ -60,9 +60,9 @@ aliases = ["/v1.1", "/guides/reference/admin", "/v3.1"]

Provisioning

A guide to help you automate your Grafana setup & configuration.

- }}" class="nav-cards__item nav-cards__item--guide"> -

What's new in v6.2

-

Article on all the new cool features and enhancements in v6.2

+
}}" class="nav-cards__item nav-cards__item--guide"> +

What's new in v6.3

+

Article on all the new cool features and enhancements in v6.3

}}" class="nav-cards__item nav-cards__item--guide">

Screencasts

diff --git a/docs/sources/auth/github.md b/docs/sources/auth/github.md index 22a10044d93..ec1eda00232 100644 --- a/docs/sources/auth/github.md +++ b/docs/sources/auth/github.md @@ -99,3 +99,18 @@ allow_sign_up = true allowed_organizations = github google ``` +### Team Sync (Enterprise only) + +> Only available in Grafana Enterprise v6.3+ + +With Team Sync you can map your GitHub org teams to teams in Grafana so that your users will automatically be added to +the correct teams. + +Your GitHub teams can be referenced in two ways: + +- `https://github.com/orgs//teams/` +- `@/` + +Example: `@grafana/developers` + +[Learn more about Team Sync]({{< relref "auth/enhanced_ldap.md" >}}) diff --git a/docs/sources/guides/whats-new-in-v6-3.md b/docs/sources/guides/whats-new-in-v6-3.md new file mode 100644 index 00000000000..bfb2642cbaf --- /dev/null +++ b/docs/sources/guides/whats-new-in-v6-3.md @@ -0,0 +1,144 @@ ++++ +title = "What's New in Grafana v6.3" +description = "Feature & improvement highlights for Grafana v6.3" +keywords = ["grafana", "new", "documentation", "6.3"] +type = "docs" +[menu.docs] +name = "Version 6.3" +identifier = "v6.3" +parent = "whatsnew" +weight = -14 ++++ + +# What's New in Grafana v6.3 + +For all details please read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) + +## Highlights + +- New Explore features + - [Loki Live Streaming]({{< relref "#loki-live-streaming" >}}) + - [Loki Context Queries]({{< relref "#loki-context-queries" >}}) + - [Elasticsearch Logs Support]({{< relref "#elasticsearch-logs-support" >}}) + - [InfluxDB Logs Support]({{< relref "#influxdb-logs-support" >}}) +- [Data links]({{< relref "#data-links" >}}) +- [New Time Picker]({{< relref "#new-time-picker" >}}) +- [Graph Area Gradients]({{< relref "#graph-gradients" >}}) - A new graph display option! +- Grafana Enterprise + - [LDAP Active Sync]({{< relref "#ldap-active-sync" >}}) - LDAP Active Sync + - [SAML Authentication]({{< relref "#saml-authentication" >}}) - SAML Authentication + +## Explore improvements + +This release adds a ton of enhancements to Explore. Both in terms of new general enhancements but also in +new data source specific features. + +### Loki live streaming + +For log queries using the Loki data source you can now stream logs live directly to the Explore UI. + +### Loki context queries + +After finding a log line through the heavy use of query filters it can then be useful to +see the log lines surrounding the line your searched for. The `show context` feature +allows you to view lines before and after the line of interest. + +### Elasticsearch logs support + +This release adds support for searching & visualizing logs stored in Elasticsearch in the Explore mode. With a special +simplified query interface specifically designed for logs search. + +{{< docs-imagebox img="/img/docs/v63/elasticsearch_explore_logs.png" max-width="600px" caption="New Time Picker" >}} + +Please read [Using Elasticsearch in Grafana](/features/datasources/elasticsearch/#querying-logs-beta) for more detailed information on how to get started and use it. + +### InfluxDB logs support + +This release adds support for searching & visualizing logs stored in InfluxDB in the Explore mode. With a special +simplified query interface specifically designed for logs search. + +{{< docs-imagebox img="/img/docs/v63/influxdb_explore_logs.png" max-width="600px" caption="New Time Picker" >}} + +Please read [Using InfluxDB in Grafana](/features/datasources/influxdb/#querying-logs-beta) for more detailed information on how to get started and use it. + +## Data Links + +We have simplified the UI for defining panel drilldown links (and renamed them to Panel links). We have also added a +new type of link named `Data link`. The reason to have two different types is to make it clear how they are used +and what variables you can use in the link. Panel links are only shown in the top left corner of +the panel and you cannot reference series name or any data field. + +While `Data links` are used by the actual visualization and can reference data fields. + +Example: +```url +http://my-grafana.com/d/bPCI6VSZz/other-dashboard?var-server=${__series_name} +``` + +You have access to these variables: + +Name | Description +------------ | ------------- +*${__series_name}* | The name of the time series (or table) +*${__value_time}* | The time of the point your clicking on (in millisecond epoch) +*${__url_time_range}* | Interpolates as the full time range (i.e. from=21312323412&to=21312312312) +*${__all_variables}* | Adds all current variables (and current values) to the url + +You can then click on point in the Graph. + +{{< docs-imagebox img="/img/docs/v63/graph_datalink.png" max-width="400px" caption="New Time Picker" >}} + +For now only the Graph panel supports `Data links` but we hope to add these to many visualizations. + +## New Time Picker + +The time picker has been re-designed and with a more basic design that makes accessing quick ranges more easy. + +{{< docs-imagebox img="/img/docs/v63/time_picker.png" max-width="400px" caption="New Time Picker" >}} + +## Graph Gradients + +Want more eye candy in your graphs? Then the fill gradient option might be for you! Works really well for +graphs with only a single series. + +{{< docs-imagebox img="/img/docs/v63/graph_gradient_area.jpeg" max-width="800px" caption="Graph Gradient Area" >}} + +Looks really nice in light theme as well. + +{{< docs-imagebox img="/img/docs/v63/graph_gradients_white.png" max-width="800px" caption="Graph Gradient Area" >}} + +## Grafana Enterprise + +Substantial refactoring and improvements to the external auth systems has gone in to this release making the features +listed below possible as well as laying a foundation for future enhancements. + +### LDAP Active Sync + +This is a new Enterprise feature that enables background syncing of user information, org role and teams memberships. +This syncing is otherwise only done at login time. With this feature you can schedule how often this user synchronization should +occur. + +For example, lets say a user is removed from an LDAP group. In previous versions of Grafana an admin would have to +wait for the user to logout or the session to expire for the Grafana permissions to update, a process that can take days. + +With active sync the user would be automatically removed from the corresponding team in Grafana or even logged out and disabled if no longer +belonging to an LDAP group that gives them access to Grafana. + +[Read more](/auth/enhanced_ldap/#active-ldap-synchronization) + +### SAML Authentication + +Built-in support for SAML is now available in Grafana Enterprise. + +### Team Sync for GitHub OAuth + +When setting up OAuth with GitHub it's now possible to sync GitHub teams with Teams in Grafana. + +[See docs]({{< relref "auth/github.md" >}}) + +### Team Sync for Auth Proxy + +We've added support for enriching the Auth Proxy headers with Teams information, which makes it possible +to use Team Sync with Auth Proxy. + +[See docs](/auth/auth-proxy/#auth-proxy-authentication) From ffa9429c68347222e0b122710c2bc9a663c93b4f Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Wed, 10 Jul 2019 13:46:33 +0200 Subject: [PATCH 9/9] Fix unused variable errors (#18030) --- packages/grafana-toolkit/src/cli/tasks/package.build.ts | 1 - packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/grafana-toolkit/src/cli/tasks/package.build.ts b/packages/grafana-toolkit/src/cli/tasks/package.build.ts index fcf1e75472d..c52c098c32f 100644 --- a/packages/grafana-toolkit/src/cli/tasks/package.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/package.build.ts @@ -3,7 +3,6 @@ import execa = require('execa'); import * as fs from 'fs'; // @ts-ignore import * as path from 'path'; -import { changeCwdToGrafanaUi, restoreCwd, changeCwdToPackage } from '../utils/cwd'; import chalk from 'chalk'; import { useSpinner } from '../utils/useSpinner'; import { Task, TaskRunner } from './task'; diff --git a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts index f76ea28e7d4..68f53cb8668 100644 --- a/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts +++ b/packages/grafana-toolkit/src/cli/tasks/toolkit.build.ts @@ -1,6 +1,5 @@ import execa = require('execa'); import * as fs from 'fs'; -import { restoreCwd, changeCwdToGrafanaToolkit } from '../utils/cwd'; import chalk from 'chalk'; import { useSpinner } from '../utils/useSpinner'; import { Task, TaskRunner } from './task';