Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

DataProxy: Adds oauth pass-through option for datasources #15205

Merged
merged 21 commits into from Mar 25, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5a59cdf
Add oauth pass-thru option for datasources
seanlaff Feb 2, 2019
4a7cf82
Remove length from text columns
seanlaff Feb 2, 2019
fa22311
base64 encode encrypted oauth token fields
seanlaff Feb 2, 2019
d922285
Move oauth token migrations in user_auth_mig
seanlaff Mar 11, 2019
f17307b
Always return most recently used auth_module from GetAuthInfo
seanlaff Mar 13, 2019
3b15e11
Get most recent oauth token from db, rather than lookup by auth_module
seanlaff Mar 13, 2019
4324a7f
Remove auth module from ds_proxy oauth test
seanlaff Mar 13, 2019
f79dc0a
Remove auth_module settings from oauthPassThru ui
seanlaff Mar 13, 2019
7e62394
Add function in ds_proxy to handle oauthPassThru headers
seanlaff Mar 13, 2019
de33833
Remove todo about index on user_id in user_auth because it exists
seanlaff Mar 13, 2019
8d19ca0
Merge branch 'master' into 12556-oauth-pass-thru
seanlaff Mar 13, 2019
8d8119a
Change import path for social since it has moved
seanlaff Mar 13, 2019
fcc18d8
Change import path for social in the tests
seanlaff Mar 13, 2019
f3c5271
Make recently used auth_module test more robust by adding another 'lo…
seanlaff Mar 14, 2019
3f9a19d
Merge branch 'master' into 12556-oauth-pass-thru
seanlaff Mar 14, 2019
dba2df1
Make all http auth setting labels the same width
seanlaff Mar 20, 2019
be6e76e
Use structured logging instead of printf
seanlaff Mar 20, 2019
b696492
Rename dispatched commands to make them easy to grok
seanlaff Mar 20, 2019
3b9b6c5
Abstract encrypt/encode and decode/decrypt into their own functions
seanlaff Mar 20, 2019
b3461c9
Remove sleeps in test code by overriding time.Now()
seanlaff Mar 20, 2019
50c5854
Use grafana's logger implementation
seanlaff Mar 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/api/login_oauth.go
Expand Up @@ -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,
Expand Down
41 changes: 41 additions & 0 deletions pkg/api/pluginproxy/ds_proxy.go
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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() {
seanlaff marked this conversation as resolved.
Show resolved Hide resolved
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)
seanlaff marked this conversation as resolved.
Show resolved Hide resolved
}

// 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))
}
}
}

Expand Down
52 changes: 52 additions & 0 deletions pkg/api/pluginproxy/ds_proxy_test.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"))
})
})
})
}

Expand Down
23 changes: 22 additions & 1 deletion pkg/login/ext_user.go
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
seanlaff marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down
26 changes: 21 additions & 5 deletions pkg/models/user_auth.go
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -67,6 +82,7 @@ type GetUserByAuthInfoQuery struct {
}

type GetAuthInfoQuery struct {
UserId int64
AuthModule string
AuthId string

Expand Down
1 change: 1 addition & 0 deletions pkg/services/sqlstore/migrations/migrations.go
Expand Up @@ -33,6 +33,7 @@ func AddMigrations(mg *Migrator) {
addUserAuthMigrations(mg)
addServerlockMigrations(mg)
addUserAuthTokenMigrations(mg)
addUserAuthOAuthMigrations(mg)
}

func addMigrationLogMigrations(mg *Migrator) {
Expand Down
25 changes: 25 additions & 0 deletions 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{
seanlaff marked this conversation as resolved.
Show resolved Hide resolved
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,
}))
mg.AddMigration("Add OAuth token type to user_auth", NewAddColumnMigration(userAuthV2, &Column{
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,
}))

mg.AddMigration("Add index to user_id column in user_auth", NewAddIndexMigration(userAuthV2, &Index{
Cols: []string{"user_id"},
}))

}