From 5a59cdf0ef4dbd1e0719fbdc469919f75b9b99cc Mon Sep 17 00:00:00 2001 From: Sean Lafferty Date: Fri, 1 Feb 2019 19:40:57 -0500 Subject: [PATCH 001/608] Add oauth pass-thru option for datasources --- pkg/api/login_oauth.go | 1 + pkg/api/pluginproxy/ds_proxy.go | 41 +++++++++ pkg/api/pluginproxy/ds_proxy_test.go | 52 +++++++++++ pkg/login/ext_user.go | 23 ++++- pkg/models/user_auth.go | 26 ++++-- .../sqlstore/migrations/migrations.go | 1 + .../migrations/user_auth_oauth_mig.go | 25 ++++++ pkg/services/sqlstore/user_auth.go | 86 ++++++++++++++++++- pkg/services/sqlstore/user_auth_test.go | 43 ++++++++++ pkg/social/social.go | 1 + .../datasources/partials/http_settings.html | 13 +++ .../datasources/settings/HttpSettingsCtrl.ts | 7 ++ 12 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 pkg/services/sqlstore/migrations/user_auth_oauth_mig.go diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 4160d48733ea8..5320c63fbeb7b 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -165,6 +165,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) { extUser := &m.ExternalUserInfo{ AuthModule: "oauth_" + name, + OAuthToken: token, AuthId: userInfo.Id, Name: userInfo.Name, Login: userInfo.Login, diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index a0ad96a697775..b7112f258dabc 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -14,11 +14,14 @@ import ( "time" "github.com/opentracing/opentracing-go" + "golang.org/x/oauth2" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/util" ) @@ -215,6 +218,44 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) { if proxy.route != nil { ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds) } + + if proxy.ds.JsonData != nil && proxy.ds.JsonData.Get("oauthPassThru").MustBool() { + provider := proxy.ds.JsonData.Get("oauthPassThruProvider").MustString() + connect, ok := social.SocialMap[strings.TrimPrefix(provider, "oauth_")] // The socialMap keys don't have "oauth_" prefix, but everywhere else in the system does + if !ok { + logger.Error("Failed to find oauth provider with given name", "provider", provider) + } + cmd := &m.GetAuthInfoQuery{UserId: proxy.ctx.UserId, AuthModule: provider} + if err := bus.Dispatch(cmd); err != nil { + logger.Error("Error feching oauth information for user", "error", err) + } + + // TokenSource handles refreshing the token if it has expired + token, err := connect.TokenSource(proxy.ctx.Req.Context(), &oauth2.Token{ + AccessToken: cmd.Result.OAuthAccessToken, + Expiry: cmd.Result.OAuthExpiry, + RefreshToken: cmd.Result.OAuthRefreshToken, + TokenType: cmd.Result.OAuthTokenType, + }).Token() + if err != nil { + logger.Error("Failed to retrieve access token from oauth provider", "provider", cmd.Result.AuthModule) + } + + // If the tokens are not the same, update the entry in the DB + if token.AccessToken != cmd.Result.OAuthAccessToken { + cmd2 := &m.UpdateAuthInfoCommand{ + UserId: cmd.Result.Id, + AuthModule: cmd.Result.AuthModule, + AuthId: cmd.Result.AuthId, + OAuthToken: token, + } + if err := bus.Dispatch(cmd2); err != nil { + logger.Error("Failed to update access token during token refresh", "error", err) + } + } + req.Header.Del("Authorization") + req.Header.Add("Authorization", fmt.Sprintf("%s %s", token.Type(), token.AccessToken)) + } } } diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index c9be169565f7e..e4d0c91a3dfef 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -9,13 +9,16 @@ import ( "testing" "time" + "golang.org/x/oauth2" macaron "gopkg.in/macaron.v1" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/util" . "github.com/smartystreets/goconvey/convey" ) @@ -388,6 +391,55 @@ func TestDSRouteRule(t *testing.T) { So(req.Header.Get("X-Canary"), ShouldEqual, "stillthere") }) }) + + Convey("When proxying a datasource that has oauth token pass-thru enabled", func() { + social.SocialMap["generic_oauth"] = &social.SocialGenericOAuth{ + SocialBase: &social.SocialBase{ + Config: &oauth2.Config{}, + }, + } + + bus.AddHandler("test", func(query *m.GetAuthInfoQuery) error { + query.Result = &m.UserAuth{ + Id: 1, + UserId: 1, + AuthModule: "generic_oauth", + OAuthAccessToken: "testtoken", + OAuthRefreshToken: "testrefreshtoken", + OAuthTokenType: "Bearer", + OAuthExpiry: time.Now().AddDate(0, 0, 1), + } + return nil + }) + + plugin := &plugins.DataSourcePlugin{} + ds := &m.DataSource{ + Type: "custom-datasource", + Url: "http://host/root/", + JsonData: simplejson.NewFromAny(map[string]interface{}{ + "oauthPassThru": true, + "oauthPassThruProvider": "oauth_generic_oauth", + }), + } + + req, _ := http.NewRequest("GET", "http://localhost/asd", nil) + ctx := &m.ReqContext{ + SignedInUser: &m.SignedInUser{UserId: 1}, + Context: &macaron.Context{ + Req: macaron.Request{Request: req}, + }, + } + proxy := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/") + req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) + + So(err, ShouldBeNil) + + proxy.getDirector()(req) + + Convey("Should have access token in header", func() { + So(req.Header.Get("Authorization"), ShouldEqual, fmt.Sprintf("%s %s", "Bearer", "testtoken")) + }) + }) }) } diff --git a/pkg/login/ext_user.go b/pkg/login/ext_user.go index 42fb37ff9d0c8..22802f8b5cd99 100644 --- a/pkg/login/ext_user.go +++ b/pkg/login/ext_user.go @@ -51,11 +51,12 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { return err } - if extUser.AuthModule != "" && extUser.AuthId != "" { + if extUser.AuthModule != "" { cmd2 := &m.SetAuthInfoCommand{ UserId: cmd.Result.Id, AuthModule: extUser.AuthModule, AuthId: extUser.AuthId, + OAuthToken: extUser.OAuthToken, } if err := bus.Dispatch(cmd2); err != nil { return err @@ -69,6 +70,14 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { if err != nil { return err } + + // Always persist the latest token at log-in + if extUser.AuthModule != "" && extUser.OAuthToken != nil { + err = updateUserAuth(cmd.Result, extUser) + if err != nil { + return err + } + } } err = syncOrgRoles(cmd.Result, extUser) @@ -143,6 +152,18 @@ func updateUser(user *m.User, extUser *m.ExternalUserInfo) error { return bus.Dispatch(updateCmd) } +func updateUserAuth(user *m.User, extUser *m.ExternalUserInfo) error { + updateCmd := &m.UpdateAuthInfoCommand{ + AuthModule: extUser.AuthModule, + AuthId: extUser.AuthId, + UserId: user.Id, + OAuthToken: extUser.OAuthToken, + } + + log.Debug("Updating user_auth info for user_id %d", user.Id) + return bus.Dispatch(updateCmd) +} + func syncOrgRoles(user *m.User, extUser *m.ExternalUserInfo) error { // don't sync org roles if none are specified if len(extUser.OrgRoles) == 0 { diff --git a/pkg/models/user_auth.go b/pkg/models/user_auth.go index 28189005737cb..11018c7fd716d 100644 --- a/pkg/models/user_auth.go +++ b/pkg/models/user_auth.go @@ -2,17 +2,24 @@ package models import ( "time" + + "golang.org/x/oauth2" ) type UserAuth struct { - Id int64 - UserId int64 - AuthModule string - AuthId string - Created time.Time + Id int64 + UserId int64 + AuthModule string + AuthId string + Created time.Time + OAuthAccessToken string + OAuthRefreshToken string + OAuthTokenType string + OAuthExpiry time.Time } type ExternalUserInfo struct { + OAuthToken *oauth2.Token AuthModule string AuthId string UserId int64 @@ -39,6 +46,14 @@ type SetAuthInfoCommand struct { AuthModule string AuthId string UserId int64 + OAuthToken *oauth2.Token +} + +type UpdateAuthInfoCommand struct { + AuthModule string + AuthId string + UserId int64 + OAuthToken *oauth2.Token } type DeleteAuthInfoCommand struct { @@ -67,6 +82,7 @@ type GetUserByAuthInfoQuery struct { } type GetAuthInfoQuery struct { + UserId int64 AuthModule string AuthId string diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 931259ec3edb7..864377033c3f0 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -33,6 +33,7 @@ func AddMigrations(mg *Migrator) { addUserAuthMigrations(mg) addServerlockMigrations(mg) addUserAuthTokenMigrations(mg) + addUserAuthOAuthMigrations(mg) } func addMigrationLogMigrations(mg *Migrator) { diff --git a/pkg/services/sqlstore/migrations/user_auth_oauth_mig.go b/pkg/services/sqlstore/migrations/user_auth_oauth_mig.go new file mode 100644 index 0000000000000..beaf7a29a27a7 --- /dev/null +++ b/pkg/services/sqlstore/migrations/user_auth_oauth_mig.go @@ -0,0 +1,25 @@ +package migrations + +import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +func addUserAuthOAuthMigrations(mg *Migrator) { + userAuthV2 := Table{Name: "user_auth"} + + mg.AddMigration("Add OAuth access token to user_auth", NewAddColumnMigration(userAuthV2, &Column{ + Name: "o_auth_access_token", Type: DB_Text, Nullable: true, Length: 255, + })) + mg.AddMigration("Add OAuth refresh token to user_auth", NewAddColumnMigration(userAuthV2, &Column{ + Name: "o_auth_refresh_token", Type: DB_Text, Nullable: true, Length: 255, + })) + mg.AddMigration("Add OAuth token type to user_auth", NewAddColumnMigration(userAuthV2, &Column{ + Name: "o_auth_token_type", Type: DB_Text, Nullable: true, Length: 255, + })) + mg.AddMigration("Add OAuth expiry to user_auth", NewAddColumnMigration(userAuthV2, &Column{ + Name: "o_auth_expiry", Type: DB_DateTime, Nullable: true, + })) + + mg.AddMigration("Add index to user_id column in user_auth", NewAddIndexMigration(userAuthV2, &Index{ + Cols: []string{"user_id"}, + })) + +} diff --git a/pkg/services/sqlstore/user_auth.go b/pkg/services/sqlstore/user_auth.go index aec828451a463..d9d8b8ee59b64 100644 --- a/pkg/services/sqlstore/user_auth.go +++ b/pkg/services/sqlstore/user_auth.go @@ -5,12 +5,15 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) func init() { bus.AddHandler("sql", GetUserByAuthInfo) bus.AddHandler("sql", GetAuthInfo) bus.AddHandler("sql", SetAuthInfo) + bus.AddHandler("sql", UpdateAuthInfo) bus.AddHandler("sql", DeleteAuthInfo) } @@ -94,7 +97,7 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { } // create authInfo record to link accounts - if authQuery.Result == nil && query.AuthModule != "" && query.AuthId != "" { + if authQuery.Result == nil && query.AuthModule != "" { cmd2 := &m.SetAuthInfoCommand{ UserId: user.Id, AuthModule: query.AuthModule, @@ -111,6 +114,7 @@ func GetUserByAuthInfo(query *m.GetUserByAuthInfoQuery) error { func GetAuthInfo(query *m.GetAuthInfoQuery) error { userAuth := &m.UserAuth{ + UserId: query.UserId, // TODO this doesn't have an index in the db AuthModule: query.AuthModule, AuthId: query.AuthId, } @@ -122,6 +126,28 @@ func GetAuthInfo(query *m.GetAuthInfoQuery) error { return m.ErrUserNotFound } + if userAuth.OAuthAccessToken != "" { + accessToken, err := util.Decrypt([]byte(userAuth.OAuthAccessToken), setting.SecretKey) + if err != nil { + return err + } + userAuth.OAuthAccessToken = string(accessToken) + } + if userAuth.OAuthRefreshToken != "" { + refreshToken, err := util.Decrypt([]byte(userAuth.OAuthRefreshToken), setting.SecretKey) + if err != nil { + return err + } + userAuth.OAuthRefreshToken = string(refreshToken) + } + if userAuth.OAuthTokenType != "" { + tokenType, err := util.Decrypt([]byte(userAuth.OAuthTokenType), setting.SecretKey) + if err != nil { + return err + } + userAuth.OAuthTokenType = string(tokenType) + } + query.Result = userAuth return nil } @@ -135,11 +161,69 @@ func SetAuthInfo(cmd *m.SetAuthInfoCommand) error { Created: time.Now(), } + if cmd.OAuthToken != nil { + secretAccessToken, err := util.Encrypt([]byte(cmd.OAuthToken.AccessToken), setting.SecretKey) + if err != nil { + return err + } + secretRefreshToken, err := util.Encrypt([]byte(cmd.OAuthToken.RefreshToken), setting.SecretKey) + if err != nil { + return err + } + secretTokenType, err := util.Encrypt([]byte(cmd.OAuthToken.TokenType), setting.SecretKey) + if err != nil { + return err + } + + authUser.OAuthAccessToken = string(secretAccessToken) + authUser.OAuthRefreshToken = string(secretRefreshToken) + authUser.OAuthTokenType = string(secretTokenType) + authUser.OAuthExpiry = cmd.OAuthToken.Expiry + } + _, err := sess.Insert(authUser) return err }) } +func UpdateAuthInfo(cmd *m.UpdateAuthInfoCommand) error { + return inTransaction(func(sess *DBSession) error { + authUser := &m.UserAuth{ + UserId: cmd.UserId, + AuthModule: cmd.AuthModule, + AuthId: cmd.AuthId, + Created: time.Now(), + } + + if cmd.OAuthToken != nil { + secretAccessToken, err := util.Encrypt([]byte(cmd.OAuthToken.AccessToken), setting.SecretKey) + if err != nil { + return err + } + secretRefreshToken, err := util.Encrypt([]byte(cmd.OAuthToken.RefreshToken), setting.SecretKey) + if err != nil { + return err + } + secretTokenType, err := util.Encrypt([]byte(cmd.OAuthToken.TokenType), setting.SecretKey) + if err != nil { + return err + } + authUser.OAuthAccessToken = string(secretAccessToken) + authUser.OAuthRefreshToken = string(secretRefreshToken) + authUser.OAuthTokenType = string(secretTokenType) + authUser.OAuthExpiry = cmd.OAuthToken.Expiry + } + + cond := &m.UserAuth{ + UserId: cmd.UserId, + AuthModule: cmd.AuthModule, + } + + _, err := sess.Update(authUser, cond) + return err + }) +} + func DeleteAuthInfo(cmd *m.DeleteAuthInfoCommand) error { return inTransaction(func(sess *DBSession) error { _, err := sess.Delete(cmd.UserAuth) diff --git a/pkg/services/sqlstore/user_auth_test.go b/pkg/services/sqlstore/user_auth_test.go index a0dd714fe6f7b..b112765bb504a 100644 --- a/pkg/services/sqlstore/user_auth_test.go +++ b/pkg/services/sqlstore/user_auth_test.go @@ -4,8 +4,10 @@ import ( "context" "fmt" "testing" + "time" . "github.com/smartystreets/goconvey/convey" + "golang.org/x/oauth2" m "github.com/grafana/grafana/pkg/models" ) @@ -126,5 +128,46 @@ func TestUserAuth(t *testing.T) { So(err, ShouldEqual, m.ErrUserNotFound) So(query.Result, ShouldBeNil) }) + + Convey("Can set & retrieve oauth token information", func() { + token := &oauth2.Token{ + AccessToken: "testaccess", + RefreshToken: "testrefresh", + Expiry: time.Now(), + TokenType: "Bearer", + } + + // Find a user to set tokens on + login := "loginuser0" + + // Calling GetUserByAuthInfoQuery on an existing user will populate an entry in the user_auth table + query := &m.GetUserByAuthInfoQuery{Login: login, AuthModule: "test", AuthId: "test"} + err = GetUserByAuthInfo(query) + + So(err, ShouldBeNil) + So(query.Result.Login, ShouldEqual, login) + + cmd := &m.UpdateAuthInfoCommand{ + UserId: query.Result.Id, + AuthId: query.AuthId, + AuthModule: query.AuthModule, + OAuthToken: token, + } + err = UpdateAuthInfo(cmd) + + So(err, ShouldBeNil) + + getAuthQuery := &m.GetAuthInfoQuery{ + UserId: query.Result.Id, + } + + err = GetAuthInfo(getAuthQuery) + + So(err, ShouldBeNil) + So(getAuthQuery.Result.OAuthAccessToken, ShouldEqual, token.AccessToken) + So(getAuthQuery.Result.OAuthRefreshToken, ShouldEqual, token.RefreshToken) + So(getAuthQuery.Result.OAuthTokenType, ShouldEqual, token.TokenType) + + }) }) } diff --git a/pkg/social/social.go b/pkg/social/social.go index 60099a028d6de..3ec0e2c966465 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -31,6 +31,7 @@ type SocialConnector interface { AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string Exchange(ctx context.Context, code string) (*oauth2.Token, error) Client(ctx context.Context, t *oauth2.Token) *http.Client + TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource } type SocialBase struct { diff --git a/public/app/features/datasources/partials/http_settings.html b/public/app/features/datasources/partials/http_settings.html index 521e2d3cdc695..78fb24c6093b5 100644 --- a/public/app/features/datasources/partials/http_settings.html +++ b/public/app/features/datasources/partials/http_settings.html @@ -87,6 +87,19 @@

Auth

+
+ +
+ + +
+
OAuth Identity Forwarding Details
+
+ OAuth Source +
+ +
+
diff --git a/public/app/features/datasources/settings/HttpSettingsCtrl.ts b/public/app/features/datasources/settings/HttpSettingsCtrl.ts index 47022c283f8af..e26fc5ab719c1 100644 --- a/public/app/features/datasources/settings/HttpSettingsCtrl.ts +++ b/public/app/features/datasources/settings/HttpSettingsCtrl.ts @@ -20,6 +20,13 @@ coreModule.directive('datasourceHttpSettings', () => { $scope.getSuggestUrls = () => { return [$scope.suggestUrl]; }; + $scope.oauthProviders = [ + { key: 'oauth_google', value: 'Google OAuth' }, + { key: 'oauth_gitlab', value: 'GitLab OAuth' }, + { key: 'oauth_generic_oauth', value: 'Generic OAuth' }, + { key: 'oauth_grafana_com', value: 'Grafana OAuth' }, + { key: 'oauth_github', value: 'GitHub OAuth' }, + ]; }, }, }; From 4a7cf82f5fe5317bd0bb8e83664918d7101cffde Mon Sep 17 00:00:00 2001 From: Sean Lafferty Date: Fri, 1 Feb 2019 19:45:27 -0500 Subject: [PATCH 002/608] Remove length from text columns --- pkg/services/sqlstore/migrations/user_auth_oauth_mig.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/services/sqlstore/migrations/user_auth_oauth_mig.go b/pkg/services/sqlstore/migrations/user_auth_oauth_mig.go index beaf7a29a27a7..039660f36129c 100644 --- a/pkg/services/sqlstore/migrations/user_auth_oauth_mig.go +++ b/pkg/services/sqlstore/migrations/user_auth_oauth_mig.go @@ -6,13 +6,13 @@ func addUserAuthOAuthMigrations(mg *Migrator) { userAuthV2 := Table{Name: "user_auth"} mg.AddMigration("Add OAuth access token to user_auth", NewAddColumnMigration(userAuthV2, &Column{ - Name: "o_auth_access_token", Type: DB_Text, Nullable: true, Length: 255, + Name: "o_auth_access_token", Type: DB_Text, Nullable: true, })) mg.AddMigration("Add OAuth refresh token to user_auth", NewAddColumnMigration(userAuthV2, &Column{ - Name: "o_auth_refresh_token", Type: DB_Text, Nullable: true, Length: 255, + Name: "o_auth_refresh_token", Type: DB_Text, Nullable: true, })) mg.AddMigration("Add OAuth token type to user_auth", NewAddColumnMigration(userAuthV2, &Column{ - Name: "o_auth_token_type", Type: DB_Text, Nullable: true, Length: 255, + Name: "o_auth_token_type", Type: DB_Text, Nullable: true, })) mg.AddMigration("Add OAuth expiry to user_auth", NewAddColumnMigration(userAuthV2, &Column{ Name: "o_auth_expiry", Type: DB_DateTime, Nullable: true, From fa22311a958ae3114e9ed1f64d352aae88e350a5 Mon Sep 17 00:00:00 2001 From: Sean Lafferty Date: Sat, 2 Feb 2019 09:03:04 -0500 Subject: [PATCH 003/608] base64 encode encrypted oauth token fields --- pkg/services/sqlstore/user_auth.go | 38 ++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/pkg/services/sqlstore/user_auth.go b/pkg/services/sqlstore/user_auth.go index d9d8b8ee59b64..7868c470bb920 100644 --- a/pkg/services/sqlstore/user_auth.go +++ b/pkg/services/sqlstore/user_auth.go @@ -1,6 +1,7 @@ package sqlstore import ( + "encoding/base64" "time" "github.com/grafana/grafana/pkg/bus" @@ -127,25 +128,38 @@ func GetAuthInfo(query *m.GetAuthInfoQuery) error { } if userAuth.OAuthAccessToken != "" { - accessToken, err := util.Decrypt([]byte(userAuth.OAuthAccessToken), setting.SecretKey) + decodedAccessToken, err := base64.StdEncoding.DecodeString(userAuth.OAuthAccessToken) if err != nil { return err } - userAuth.OAuthAccessToken = string(accessToken) + decryptedAccessToken, err := util.Decrypt(decodedAccessToken, setting.SecretKey) + if err != nil { + return err + } + userAuth.OAuthAccessToken = string(decryptedAccessToken) + } if userAuth.OAuthRefreshToken != "" { - refreshToken, err := util.Decrypt([]byte(userAuth.OAuthRefreshToken), setting.SecretKey) + decodedRefreshToken, err := base64.StdEncoding.DecodeString(userAuth.OAuthRefreshToken) + if err != nil { + return err + } + decryptedRefreshToken, err := util.Decrypt(decodedRefreshToken, setting.SecretKey) if err != nil { return err } - userAuth.OAuthRefreshToken = string(refreshToken) + userAuth.OAuthRefreshToken = string(decryptedRefreshToken) } if userAuth.OAuthTokenType != "" { - tokenType, err := util.Decrypt([]byte(userAuth.OAuthTokenType), setting.SecretKey) + decodedTokenType, err := base64.StdEncoding.DecodeString(userAuth.OAuthTokenType) + if err != nil { + return err + } + decryptedTokenType, err := util.Decrypt(decodedTokenType, setting.SecretKey) if err != nil { return err } - userAuth.OAuthTokenType = string(tokenType) + userAuth.OAuthTokenType = string(decryptedTokenType) } query.Result = userAuth @@ -175,9 +189,9 @@ func SetAuthInfo(cmd *m.SetAuthInfoCommand) error { return err } - authUser.OAuthAccessToken = string(secretAccessToken) - authUser.OAuthRefreshToken = string(secretRefreshToken) - authUser.OAuthTokenType = string(secretTokenType) + authUser.OAuthAccessToken = base64.StdEncoding.EncodeToString(secretAccessToken) + authUser.OAuthRefreshToken = base64.StdEncoding.EncodeToString(secretRefreshToken) + authUser.OAuthTokenType = base64.StdEncoding.EncodeToString(secretTokenType) authUser.OAuthExpiry = cmd.OAuthToken.Expiry } @@ -208,9 +222,9 @@ func UpdateAuthInfo(cmd *m.UpdateAuthInfoCommand) error { if err != nil { return err } - authUser.OAuthAccessToken = string(secretAccessToken) - authUser.OAuthRefreshToken = string(secretRefreshToken) - authUser.OAuthTokenType = string(secretTokenType) + authUser.OAuthAccessToken = base64.StdEncoding.EncodeToString(secretAccessToken) + authUser.OAuthRefreshToken = base64.StdEncoding.EncodeToString(secretRefreshToken) + authUser.OAuthTokenType = base64.StdEncoding.EncodeToString(secretTokenType) authUser.OAuthExpiry = cmd.OAuthToken.Expiry } From 9fc87e417447a280ea8dd02428124b213069fa4b Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 5 Feb 2019 15:12:04 +0100 Subject: [PATCH 004/608] first working draft --- .../grafana-ui/src/components/Gauge/Gauge.tsx | 20 ++++---- public/app/plugins/panel/gauge/GaugePanel.tsx | 49 +++++++++++++++---- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/packages/grafana-ui/src/components/Gauge/Gauge.tsx b/packages/grafana-ui/src/components/Gauge/Gauge.tsx index 04d89bf3f57ba..9842903b394d9 100644 --- a/packages/grafana-ui/src/components/Gauge/Gauge.tsx +++ b/packages/grafana-ui/src/components/Gauge/Gauge.tsx @@ -184,17 +184,15 @@ export class Gauge extends PureComponent { const { height, width } = this.props; return ( -
-
(this.canvasElement = element)} - /> -
+
(this.canvasElement = element)} + /> ); } } diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index b6f37dde94fe8..928f9a4390917 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -16,6 +16,7 @@ interface Props extends PanelProps {} export class GaugePanel extends PureComponent { render() { + console.log('renduru'); const { panelData, width, height, onInterpolate, options } = this.props; const prefix = onInterpolate(options.prefix); @@ -28,7 +29,33 @@ export class GaugePanel extends PureComponent { nullValueMode: NullValueMode.Null, }); - if (vmSeries[0]) { + const gauges = []; + if (vmSeries.length > 1) { + for (let i = 0; i < vmSeries.length; i++) { + gauges.push( + + {theme => ( +
+ +
Gauge {i}
+
+ )} +
+ ); + } + return [gauges]; + } else if (vmSeries.length > 0) { value = vmSeries[0].stats[options.stat]; } else { value = null; @@ -40,15 +67,17 @@ export class GaugePanel extends PureComponent { return ( {theme => ( - +
+ +
)}
); From 9d3d1bc669043f5a1e7228c35d9408436491dbe2 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 5 Feb 2019 16:34:01 +0100 Subject: [PATCH 005/608] refactor panel --- public/app/core/utils/ConfigProvider.tsx | 2 +- public/app/plugins/panel/gauge/GaugePanel.tsx | 111 +++++++++--------- 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx index 6883401ad274a..1a7c394434d6b 100644 --- a/public/app/core/utils/ConfigProvider.tsx +++ b/public/app/core/utils/ConfigProvider.tsx @@ -14,7 +14,7 @@ export const provideConfig = (component: React.ComponentType) => { }; interface ThemeProviderProps { - children: (theme: GrafanaTheme) => JSX.Element; + children: (theme: GrafanaTheme) => JSX.Element | JSX.Element[]; } export const ThemeProvider = ({ children }: ThemeProviderProps) => { diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx index 928f9a4390917..040a06eed5cf4 100644 --- a/public/app/plugins/panel/gauge/GaugePanel.tsx +++ b/public/app/plugins/panel/gauge/GaugePanel.tsx @@ -9,77 +9,82 @@ import { Gauge } from '@grafana/ui'; // Types import { GaugeOptions } from './types'; -import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types'; +import { PanelProps, NullValueMode } from '@grafana/ui/src/types'; import { ThemeProvider } from 'app/core/utils/ConfigProvider'; interface Props extends PanelProps {} export class GaugePanel extends PureComponent { - render() { - console.log('renduru'); - const { panelData, width, height, onInterpolate, options } = this.props; + renderMultipleGauge(vmSeries, theme) { + const { options, width } = this.props; + const gauges = []; + + for (let i = 0; i < vmSeries.length; i++) { + const singleStatWidth = 1 / vmSeries.length * 100; + const gaugeWidth = Math.floor(width / vmSeries.length) - 10; // make Gauge slightly smaller than panel. + + gauges.push( +
+ {this.renderGauge(vmSeries[i].stats[options.stat], gaugeWidth, theme)} + +
Gauge {i}
+
+ ); + } + return gauges; + } + + renderGauge(value, width, theme) { + const { height, onInterpolate, options } = this.props; const prefix = onInterpolate(options.prefix); const suffix = onInterpolate(options.suffix); - let value: TimeSeriesValue; + return ( + + ); + } + + renderSingleGauge(timeSeries, theme) { + const { options, width } = this.props; + const timeSeriesValue = timeSeries[0].stats[options.stat]; + return
{this.renderGauge(timeSeriesValue, width, theme)}
; + } + + renderGaugeWithTableData(panelData, theme) { + const { width } = this.props; + + const firstTableDataValue = panelData.tableData.rows[0].find(prop => prop > 0); + return
{this.renderGauge(firstTableDataValue, width, theme)}
; + } + + renderPanel(theme) { + const { panelData } = this.props; if (panelData.timeSeries) { - const vmSeries = processTimeSeries({ + const timeSeries = processTimeSeries({ timeSeries: panelData.timeSeries, nullValueMode: NullValueMode.Null, }); - const gauges = []; - if (vmSeries.length > 1) { - for (let i = 0; i < vmSeries.length; i++) { - gauges.push( - - {theme => ( -
- -
Gauge {i}
-
- )} -
- ); - } - return [gauges]; - } else if (vmSeries.length > 0) { - value = vmSeries[0].stats[options.stat]; + if (timeSeries.length > 1) { + return this.renderMultipleGauge(timeSeries, theme); + } else if (timeSeries.length > 0) { + return this.renderSingleGauge(timeSeries, theme); } else { - value = null; + return null; } } else if (panelData.tableData) { - value = panelData.tableData.rows[0].find(prop => prop > 0); + return this.renderGaugeWithTableData(panelData, theme); + } else { + return
No time series data available
; } + } - return ( - - {theme => ( -
- -
- )} -
- ); + render() { + return {theme => this.renderPanel(theme)}; } } From 71cfcd58ba79e55bb483d37894a6e1e3c4a2f2e7 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 6 Feb 2019 15:38:51 +0100 Subject: [PATCH 006/608] combine mode with avg value --- .../src/components/Switch/Switch.tsx | 2 +- .../panel/gauge/GaugeOptionsEditor.tsx | 39 ++++++++++++++++--- public/app/plugins/panel/gauge/GaugePanel.tsx | 17 ++++++-- .../plugins/panel/gauge/GaugePanelOptions.tsx | 9 ++--- public/app/plugins/panel/gauge/types.ts | 1 + 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/packages/grafana-ui/src/components/Switch/Switch.tsx b/packages/grafana-ui/src/components/Switch/Switch.tsx index feee58386b889..8cdd7c481f296 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.tsx @@ -34,7 +34,7 @@ export class Switch extends PureComponent { const switchClassName = `gf-form-switch ${switchClass} ${transparent ? 'gf-form-switch--transparent' : ''}`; return ( -