grafana/pkg/api/pluginproxy/token_provider_generic.go

129 lines
3.0 KiB
Go

package pluginproxy
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"sync"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
var (
tokenCache = tokenCacheType{
cache: map[string]*jwtToken{},
}
)
type tokenCacheType struct {
cache map[string]*jwtToken
sync.Mutex
}
type genericAccessTokenProvider struct {
datasourceId int64
datasourceVersion int
route *plugins.AppPluginRoute
authParams *plugins.JwtTokenAuth
}
type jwtToken struct {
ExpiresOn time.Time
AccessToken string
}
func (token *jwtToken) UnmarshalJSON(b []byte) error {
var t struct {
AccessToken string `json:"access_token"`
ExpiresOn *json.Number `json:"expires_on"`
ExpiresIn *json.Number `json:"expires_in"`
}
if err := json.Unmarshal(b, &t); err != nil {
return err
}
token.AccessToken = t.AccessToken
token.ExpiresOn = timeNow()
if t.ExpiresOn != nil {
expiresOn, err := t.ExpiresOn.Int64()
if err != nil {
return err
}
token.ExpiresOn = time.Unix(expiresOn, 0)
} else if t.ExpiresIn != nil {
expiresIn, err := t.ExpiresIn.Int64()
if err != nil {
return err
}
token.ExpiresOn = timeNow().Add(time.Duration(expiresIn) * time.Second)
}
return nil
}
func newGenericAccessTokenProvider(ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
authParams *plugins.JwtTokenAuth) *genericAccessTokenProvider {
return &genericAccessTokenProvider{
datasourceId: ds.Id,
datasourceVersion: ds.Version,
route: pluginRoute,
authParams: authParams,
}
}
func (provider *genericAccessTokenProvider) GetAccessToken() (string, error) {
tokenCache.Lock()
defer tokenCache.Unlock()
if cachedToken, found := tokenCache.cache[provider.getAccessTokenCacheKey()]; found {
if cachedToken.ExpiresOn.After(timeNow().Add(time.Second * 10)) {
logger.Info("Using token from cache")
return cachedToken.AccessToken, nil
}
}
tokenUrl := provider.authParams.Url
params := make(url.Values)
for key, value := range provider.authParams.Params {
params.Add(key, value)
}
getTokenReq, err := http.NewRequest("POST", tokenUrl, bytes.NewBufferString(params.Encode()))
if err != nil {
return "", err
}
getTokenReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
getTokenReq.Header.Set("Content-Length", strconv.Itoa(len(params.Encode())))
resp, err := client.Do(getTokenReq)
if err != nil {
return "", err
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Warn("Failed to close response body", "err", err)
}
}()
var token jwtToken
if err := json.NewDecoder(resp.Body).Decode(&token); err != nil {
return "", err
}
tokenCache.cache[provider.getAccessTokenCacheKey()] = &token
logger.Info("Got new access token", "ExpiresOn", token.ExpiresOn)
return token.AccessToken, nil
}
func (provider *genericAccessTokenProvider) getAccessTokenCacheKey() string {
return fmt.Sprintf("%v_%v_%v_%v", provider.datasourceId, provider.datasourceVersion, provider.route.Path, provider.route.Method)
}