mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: AuthType in route configuration and params interpolation (#33674)
* AuthType in route configuration * Pass interpolated auth parameters to token provider * Unit tests * Update after review Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> Fixes #33669 Closed #33732
This commit is contained in:
parent
bfd5d3b16a
commit
1790737cf1
@ -53,7 +53,9 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
|||||||
logger.Error("Failed to set plugin route body content", "error", err)
|
logger.Error("Failed to set plugin route body content", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokenProvider := getTokenProvider(ctx, ds, route, data); tokenProvider != nil {
|
if tokenProvider, err := getTokenProvider(ctx, ds, route, data); err != nil {
|
||||||
|
logger.Error("Failed to resolve auth token provider", "error", err)
|
||||||
|
} else if tokenProvider != nil {
|
||||||
if token, err := tokenProvider.getAccessToken(); err != nil {
|
if token, err := tokenProvider.getAccessToken(); err != nil {
|
||||||
logger.Error("Failed to get access token", "error", err)
|
logger.Error("Failed to get access token", "error", err)
|
||||||
} else {
|
} else {
|
||||||
@ -65,25 +67,80 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getTokenProvider(ctx context.Context, ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
|
func getTokenProvider(ctx context.Context, ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
|
||||||
data templateData) accessTokenProvider {
|
data templateData) (accessTokenProvider, error) {
|
||||||
authenticationType := ds.JsonData.Get("authenticationType").MustString()
|
authType := pluginRoute.AuthType
|
||||||
|
|
||||||
switch authenticationType {
|
// Plugin can override authentication type specified in route configuration
|
||||||
|
if authTypeOverride := ds.JsonData.Get("authenticationType").MustString(); authTypeOverride != "" {
|
||||||
|
authType = authTypeOverride
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenAuth, err := interpolateAuthParams(pluginRoute.TokenAuth, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jwtTokenAuth, err := interpolateAuthParams(pluginRoute.JwtTokenAuth, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch authType {
|
||||||
case "gce":
|
case "gce":
|
||||||
return newGceAccessTokenProvider(ctx, ds, pluginRoute)
|
if jwtTokenAuth == nil {
|
||||||
|
return nil, fmt.Errorf("'jwtTokenAuth' not configured for authentication type '%s'", authType)
|
||||||
|
}
|
||||||
|
provider := newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
|
||||||
|
return provider, nil
|
||||||
|
|
||||||
case "jwt":
|
case "jwt":
|
||||||
if pluginRoute.JwtTokenAuth != nil {
|
if jwtTokenAuth == nil {
|
||||||
return newJwtAccessTokenProvider(ctx, ds, pluginRoute, data)
|
return nil, fmt.Errorf("'jwtTokenAuth' not configured for authentication type '%s'", authType)
|
||||||
}
|
}
|
||||||
default:
|
provider := newJwtAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
|
||||||
// Fallback to authentication options when authentication type isn't explicitly configured
|
return provider, nil
|
||||||
if pluginRoute.TokenAuth != nil {
|
|
||||||
return newGenericAccessTokenProvider(ds, pluginRoute, data)
|
case "":
|
||||||
}
|
// Fallback to authentication methods when authentication type isn't explicitly configured
|
||||||
if pluginRoute.JwtTokenAuth != nil {
|
if tokenAuth != nil {
|
||||||
return newJwtAccessTokenProvider(ctx, ds, pluginRoute, data)
|
provider := newGenericAccessTokenProvider(ds, pluginRoute, tokenAuth)
|
||||||
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
if jwtTokenAuth != nil {
|
||||||
|
provider := newJwtAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
|
||||||
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// No authentication
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("authentication type '%s' not supported", authType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func interpolateAuthParams(tokenAuth *plugins.JwtTokenAuth, data templateData) (*plugins.JwtTokenAuth, error) {
|
||||||
|
if tokenAuth == nil {
|
||||||
|
// Nothing to interpolate
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
interpolatedUrl, err := interpolateString(tokenAuth.Url, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
interpolatedParams := make(map[string]string)
|
||||||
|
for key, value := range tokenAuth.Params {
|
||||||
|
interpolatedParam, err := interpolateString(value, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
interpolatedParams[key] = interpolatedParam
|
||||||
|
}
|
||||||
|
|
||||||
|
return &plugins.JwtTokenAuth{
|
||||||
|
Url: interpolatedUrl,
|
||||||
|
Scopes: tokenAuth.Scopes,
|
||||||
|
Params: interpolatedParams,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
62
pkg/api/pluginproxy/ds_auth_provider_test.go
Normal file
62
pkg/api/pluginproxy/ds_auth_provider_test.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package pluginproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplyRoute_interpolateAuthParams(t *testing.T) {
|
||||||
|
pluginRoute := &plugins.AppPluginRoute{
|
||||||
|
Path: "pathwithjwttoken1",
|
||||||
|
URL: "https://api.jwt.io/some/path",
|
||||||
|
Method: "GET",
|
||||||
|
TokenAuth: &plugins.JwtTokenAuth{
|
||||||
|
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||||
|
Scopes: []string{
|
||||||
|
"https://www.testapi.com/auth/Read.All",
|
||||||
|
"https://www.testapi.com/auth/Write.All",
|
||||||
|
},
|
||||||
|
Params: map[string]string{
|
||||||
|
"token_uri": "{{.JsonData.tokenUri}}",
|
||||||
|
"client_email": "{{.JsonData.clientEmail}}",
|
||||||
|
"private_key": "{{.SecureJsonData.privateKey}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData := templateData{
|
||||||
|
JsonData: map[string]interface{}{
|
||||||
|
"clientEmail": "test@test.com",
|
||||||
|
"tokenUri": "login.url.com/token",
|
||||||
|
"tenantId": "f09c86ac",
|
||||||
|
},
|
||||||
|
SecureJsonData: map[string]string{
|
||||||
|
"privateKey": "testkey",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("should interpolate JwtTokenAuth struct using given JsonData", func(t *testing.T) {
|
||||||
|
interpolated, err := interpolateAuthParams(pluginRoute.TokenAuth, templateData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, interpolated)
|
||||||
|
|
||||||
|
assert.Equal(t, "https://login.server.com/f09c86ac/oauth2/token", interpolated.Url)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(interpolated.Scopes))
|
||||||
|
assert.Equal(t, "https://www.testapi.com/auth/Read.All", interpolated.Scopes[0])
|
||||||
|
assert.Equal(t, "https://www.testapi.com/auth/Write.All", interpolated.Scopes[1])
|
||||||
|
|
||||||
|
assert.Equal(t, "login.url.com/token", interpolated.Params["token_uri"])
|
||||||
|
assert.Equal(t, "test@test.com", interpolated.Params["client_email"])
|
||||||
|
assert.Equal(t, "testkey", interpolated.Params["private_key"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should return Nil if given JwtTokenAuth is Nil", func(t *testing.T) {
|
||||||
|
interpolated, err := interpolateAuthParams(pluginRoute.JwtTokenAuth, templateData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, interpolated)
|
||||||
|
})
|
||||||
|
}
|
@ -13,19 +13,22 @@ type gceAccessTokenProvider struct {
|
|||||||
datasourceVersion int
|
datasourceVersion int
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
route *plugins.AppPluginRoute
|
route *plugins.AppPluginRoute
|
||||||
|
authParams *plugins.JwtTokenAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGceAccessTokenProvider(ctx context.Context, ds *models.DataSource, pluginRoute *plugins.AppPluginRoute) *gceAccessTokenProvider {
|
func newGceAccessTokenProvider(ctx context.Context, ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
|
||||||
|
authParams *plugins.JwtTokenAuth) *gceAccessTokenProvider {
|
||||||
return &gceAccessTokenProvider{
|
return &gceAccessTokenProvider{
|
||||||
datasourceId: ds.Id,
|
datasourceId: ds.Id,
|
||||||
datasourceVersion: ds.Version,
|
datasourceVersion: ds.Version,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
route: pluginRoute,
|
route: pluginRoute,
|
||||||
|
authParams: authParams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *gceAccessTokenProvider) getAccessToken() (string, error) {
|
func (provider *gceAccessTokenProvider) getAccessToken() (string, error) {
|
||||||
tokenSrc, err := google.DefaultTokenSource(provider.ctx, provider.route.JwtTokenAuth.Scopes...)
|
tokenSrc, err := google.DefaultTokenSource(provider.ctx, provider.authParams.Scopes...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to get default token from meta data server", "error", err)
|
logger.Error("Failed to get default token from meta data server", "error", err)
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -29,7 +29,7 @@ type genericAccessTokenProvider struct {
|
|||||||
datasourceId int64
|
datasourceId int64
|
||||||
datasourceVersion int
|
datasourceVersion int
|
||||||
route *plugins.AppPluginRoute
|
route *plugins.AppPluginRoute
|
||||||
data templateData
|
authParams *plugins.JwtTokenAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
type jwtToken struct {
|
type jwtToken struct {
|
||||||
@ -69,12 +69,12 @@ func (token *jwtToken) UnmarshalJSON(b []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newGenericAccessTokenProvider(ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
|
func newGenericAccessTokenProvider(ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
|
||||||
data templateData) *genericAccessTokenProvider {
|
authParams *plugins.JwtTokenAuth) *genericAccessTokenProvider {
|
||||||
return &genericAccessTokenProvider{
|
return &genericAccessTokenProvider{
|
||||||
datasourceId: ds.Id,
|
datasourceId: ds.Id,
|
||||||
datasourceVersion: ds.Version,
|
datasourceVersion: ds.Version,
|
||||||
route: pluginRoute,
|
route: pluginRoute,
|
||||||
data: data,
|
authParams: authParams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,21 +88,14 @@ func (provider *genericAccessTokenProvider) getAccessToken() (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
urlInterpolated, err := interpolateString(provider.route.TokenAuth.Url, provider.data)
|
tokenUrl := provider.authParams.Url
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
params := make(url.Values)
|
params := make(url.Values)
|
||||||
for key, value := range provider.route.TokenAuth.Params {
|
for key, value := range provider.authParams.Params {
|
||||||
interpolatedParam, err := interpolateString(value, provider.data)
|
params.Add(key, value)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
params.Add(key, interpolatedParam)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTokenReq, err := http.NewRequest("POST", urlInterpolated, bytes.NewBufferString(params.Encode()))
|
getTokenReq, err := http.NewRequest("POST", tokenUrl, bytes.NewBufferString(params.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -28,17 +28,17 @@ type jwtAccessTokenProvider struct {
|
|||||||
datasourceVersion int
|
datasourceVersion int
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
route *plugins.AppPluginRoute
|
route *plugins.AppPluginRoute
|
||||||
data templateData
|
authParams *plugins.JwtTokenAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func newJwtAccessTokenProvider(ctx context.Context, ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
|
func newJwtAccessTokenProvider(ctx context.Context, ds *models.DataSource, pluginRoute *plugins.AppPluginRoute,
|
||||||
data templateData) *jwtAccessTokenProvider {
|
authParams *plugins.JwtTokenAuth) *jwtAccessTokenProvider {
|
||||||
return &jwtAccessTokenProvider{
|
return &jwtAccessTokenProvider{
|
||||||
datasourceId: ds.Id,
|
datasourceId: ds.Id,
|
||||||
datasourceVersion: ds.Version,
|
datasourceVersion: ds.Version,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
route: pluginRoute,
|
route: pluginRoute,
|
||||||
data: data,
|
authParams: authParams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,31 +54,19 @@ func (provider *jwtAccessTokenProvider) getAccessToken() (string, error) {
|
|||||||
|
|
||||||
conf := &jwt.Config{}
|
conf := &jwt.Config{}
|
||||||
|
|
||||||
if val, ok := provider.route.JwtTokenAuth.Params["client_email"]; ok {
|
if val, ok := provider.authParams.Params["client_email"]; ok {
|
||||||
interpolatedVal, err := interpolateString(val, provider.data)
|
conf.Email = val
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
conf.Email = interpolatedVal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := provider.route.JwtTokenAuth.Params["private_key"]; ok {
|
if val, ok := provider.authParams.Params["private_key"]; ok {
|
||||||
interpolatedVal, err := interpolateString(val, provider.data)
|
conf.PrivateKey = []byte(val)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
conf.PrivateKey = []byte(interpolatedVal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := provider.route.JwtTokenAuth.Params["token_uri"]; ok {
|
if val, ok := provider.authParams.Params["token_uri"]; ok {
|
||||||
interpolatedVal, err := interpolateString(val, provider.data)
|
conf.TokenURL = val
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
conf.TokenURL = interpolatedVal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.Scopes = provider.route.JwtTokenAuth.Scopes
|
conf.Scopes = provider.authParams.Scopes
|
||||||
|
|
||||||
token, err := getTokenSource(conf, provider.ctx)
|
token, err := getTokenSource(conf, provider.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -41,13 +41,16 @@ func TestAccessToken_pluginWithJWTTokenAuthRoute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
templateData := templateData{
|
authParams := &plugins.JwtTokenAuth{
|
||||||
JsonData: map[string]interface{}{
|
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||||
"clientEmail": "test@test.com",
|
Scopes: []string{
|
||||||
"tokenUri": "login.url.com/token",
|
"https://www.testapi.com/auth/monitoring.read",
|
||||||
|
"https://www.testapi.com/auth/cloudplatformprojects.readonly",
|
||||||
},
|
},
|
||||||
SecureJsonData: map[string]string{
|
Params: map[string]string{
|
||||||
"privateKey": "testkey",
|
"token_uri": "login.url.com/token",
|
||||||
|
"client_email": "test@test.com",
|
||||||
|
"private_key": "testkey",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +69,7 @@ func TestAccessToken_pluginWithJWTTokenAuthRoute(t *testing.T) {
|
|||||||
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||||
})
|
})
|
||||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, templateData)
|
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||||
token, err := provider.getAccessToken()
|
token, err := provider.getAccessToken()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -85,7 +88,7 @@ func TestAccessToken_pluginWithJWTTokenAuthRoute(t *testing.T) {
|
|||||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, templateData)
|
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||||
_, err := provider.getAccessToken()
|
_, err := provider.getAccessToken()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
@ -96,7 +99,7 @@ func TestAccessToken_pluginWithJWTTokenAuthRoute(t *testing.T) {
|
|||||||
AccessToken: "abc",
|
AccessToken: "abc",
|
||||||
Expiry: time.Now().Add(1 * time.Minute)}, nil
|
Expiry: time.Now().Add(1 * time.Minute)}, nil
|
||||||
})
|
})
|
||||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, templateData)
|
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||||
token1, err := provider.getAccessToken()
|
token1, err := provider.getAccessToken()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "abc", token1)
|
assert.Equal(t, "abc", token1)
|
||||||
@ -135,13 +138,18 @@ func TestAccessToken_pluginWithTokenAuthRoute(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
templateData := templateData{
|
authParams := &plugins.JwtTokenAuth{
|
||||||
JsonData: map[string]interface{}{
|
Url: server.URL + "/oauth/token",
|
||||||
"client_id": "my_client_id",
|
Scopes: []string{
|
||||||
"audience": "www.example.com",
|
"https://www.testapi.com/auth/monitoring.read",
|
||||||
|
"https://www.testapi.com/auth/cloudplatformprojects.readonly",
|
||||||
},
|
},
|
||||||
SecureJsonData: map[string]string{
|
Params: map[string]string{
|
||||||
|
"grant_type": "client_credentials",
|
||||||
|
"client_id": "my_client_id",
|
||||||
"client_secret": "my_secret",
|
"client_secret": "my_secret",
|
||||||
|
"audience": "www.example.com",
|
||||||
|
"client_name": "datasource_plugin",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +170,7 @@ func TestAccessToken_pluginWithTokenAuthRoute(t *testing.T) {
|
|||||||
|
|
||||||
mockTimeNow(time.Now())
|
mockTimeNow(time.Now())
|
||||||
defer resetTimeNow()
|
defer resetTimeNow()
|
||||||
provider := newGenericAccessTokenProvider(&models.DataSource{}, pluginRoute, templateData)
|
provider := newGenericAccessTokenProvider(&models.DataSource{}, pluginRoute, authParams)
|
||||||
|
|
||||||
testCases := []tokenTestDescription{
|
testCases := []tokenTestDescription{
|
||||||
{
|
{
|
||||||
@ -244,7 +252,7 @@ func TestAccessToken_pluginWithTokenAuthRoute(t *testing.T) {
|
|||||||
|
|
||||||
mockTimeNow(time.Now())
|
mockTimeNow(time.Now())
|
||||||
defer resetTimeNow()
|
defer resetTimeNow()
|
||||||
provider := newGenericAccessTokenProvider(&models.DataSource{}, pluginRoute, templateData)
|
provider := newGenericAccessTokenProvider(&models.DataSource{}, pluginRoute, authParams)
|
||||||
|
|
||||||
token = map[string]interface{}{
|
token = map[string]interface{}{
|
||||||
"access_token": "2YotnFZFEjr1zCsicMWpAA",
|
"access_token": "2YotnFZFEjr1zCsicMWpAA",
|
||||||
|
@ -33,6 +33,7 @@ type AppPluginRoute struct {
|
|||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
URLParams []AppPluginRouteURLParam `json:"urlParams"`
|
URLParams []AppPluginRouteURLParam `json:"urlParams"`
|
||||||
Headers []AppPluginRouteHeader `json:"headers"`
|
Headers []AppPluginRouteHeader `json:"headers"`
|
||||||
|
AuthType string `json:"authType"`
|
||||||
TokenAuth *JwtTokenAuth `json:"tokenAuth"`
|
TokenAuth *JwtTokenAuth `json:"tokenAuth"`
|
||||||
JwtTokenAuth *JwtTokenAuth `json:"jwtTokenAuth"`
|
JwtTokenAuth *JwtTokenAuth `json:"jwtTokenAuth"`
|
||||||
Body json.RawMessage `json:"body"`
|
Body json.RawMessage `json:"body"`
|
||||||
|
Loading…
Reference in New Issue
Block a user