Skip to content

Commit

Permalink
Auth: Able to use API tokens for authentication (#1662)
Browse files Browse the repository at this point in the history
* Auth: Able to use API tokens for authentication

* Update change log
  • Loading branch information
alexanderzobnin authored Jul 26, 2023
1 parent 8205f7a commit ac97694
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 55 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Change Log

## [4.4.0] - 2023-06-06
## [4.4.0] - Unreleased

### Added

- Enables PDC for zabbix datasource, [#1653](https://github.com/alexanderzobnin/grafana-zabbix/issues/1653)
- Support for secure socks proxy, [#1653](https://github.com/alexanderzobnin/grafana-zabbix/issues/1653)
- Able to use API tokens for authentication, [#1513](https://github.com/alexanderzobnin/grafana-zabbix/issues/1513)

## [4.3.1] - 2023-03-23

Expand Down
3 changes: 2 additions & 1 deletion pkg/datasource/zabbix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package datasource
import (
"github.com/alexanderzobnin/grafana-zabbix/pkg/settings"
"github.com/alexanderzobnin/grafana-zabbix/pkg/zabbix"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
Expand All @@ -13,7 +14,7 @@ var basicDatasourceInfo = &backend.DataSourceInstanceSettings{
ID: 1,
Name: "TestDatasource",
URL: "http://zabbix.org/zabbix",
JSONData: []byte(`{"username":"username", "password":"password", "cacheTTL":"10m"}`),
JSONData: []byte(`{"username":"username", "password":"password", "cacheTTL":"10m", "authType":"token"}`),
}

func mockZabbixQuery(method string, params zabbix.ZabbixAPIParams) *zabbix.ZabbixAPIRequest {
Expand Down
7 changes: 7 additions & 0 deletions pkg/settings/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ package settings

import "time"

const (
AuthTypeUserLogin = "userLogin"
AuthTypeToken = "token"
)

// ZabbixDatasourceSettingsDTO model
type ZabbixDatasourceSettingsDTO struct {
AuthType string `json:"authType"`
Trends bool `json:"trends"`
TrendsFrom string `json:"trendsFrom"`
TrendsRange string `json:"trendsRange"`
Expand All @@ -16,6 +22,7 @@ type ZabbixDatasourceSettingsDTO struct {

// ZabbixDatasourceSettings model
type ZabbixDatasourceSettings struct {
AuthType string
Trends bool
TrendsFrom time.Duration
TrendsRange time.Duration
Expand Down
11 changes: 9 additions & 2 deletions pkg/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package settings
import (
"encoding/json"
"errors"
"github.com/alexanderzobnin/grafana-zabbix/pkg/gtime"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"strconv"
"time"

"github.com/alexanderzobnin/grafana-zabbix/pkg/gtime"

"github.com/grafana/grafana-plugin-sdk-go/backend"
)

func ReadZabbixSettings(dsInstanceSettings *backend.DataSourceInstanceSettings) (*ZabbixDatasourceSettings, error) {
Expand All @@ -17,6 +19,10 @@ func ReadZabbixSettings(dsInstanceSettings *backend.DataSourceInstanceSettings)
return nil, err
}

if zabbixSettingsDTO.AuthType == "" {
zabbixSettingsDTO.AuthType = AuthTypeUserLogin
}

if zabbixSettingsDTO.TrendsFrom == "" {
zabbixSettingsDTO.TrendsFrom = "7d"
}
Expand Down Expand Up @@ -65,6 +71,7 @@ func ReadZabbixSettings(dsInstanceSettings *backend.DataSourceInstanceSettings)
}

zabbixSettings := &ZabbixDatasourceSettings{
AuthType: zabbixSettingsDTO.AuthType,
Trends: zabbixSettingsDTO.Trends,
TrendsFrom: trendsFrom,
TrendsRange: trendsRange,
Expand Down
44 changes: 32 additions & 12 deletions pkg/zabbix/zabbix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@ package zabbix

import (
"context"
"errors"
"strings"
"time"

"github.com/alexanderzobnin/grafana-zabbix/pkg/metrics"
"github.com/alexanderzobnin/grafana-zabbix/pkg/settings"
"github.com/alexanderzobnin/grafana-zabbix/pkg/zabbixapi"
"github.com/bitly/go-simplejson"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

// Zabbix is a wrapper for Zabbix API. It wraps Zabbix API queries and performs authentication, adds caching,
// deduplication and other performance optimizations.
type Zabbix struct {
api *zabbixapi.ZabbixAPI
dsInfo *backend.DataSourceInstanceSettings
cache *ZabbixCache
version int
logger log.Logger
api *zabbixapi.ZabbixAPI
dsInfo *backend.DataSourceInstanceSettings
settings *settings.ZabbixDatasourceSettings
cache *ZabbixCache
version int
logger log.Logger
}

// New returns new instance of Zabbix client.
Expand All @@ -29,10 +32,11 @@ func New(dsInfo *backend.DataSourceInstanceSettings, zabbixSettings *settings.Za
zabbixCache := NewZabbixCache(zabbixSettings.CacheTTL, 10*time.Minute)

return &Zabbix{
api: zabbixAPI,
dsInfo: dsInfo,
cache: zabbixCache,
logger: logger,
api: zabbixAPI,
dsInfo: dsInfo,
settings: zabbixSettings,
cache: zabbixCache,
logger: logger,
}, nil
}

Expand Down Expand Up @@ -90,11 +94,12 @@ func (zabbix *Zabbix) request(ctx context.Context, method string, params ZabbixA

result, err := zabbix.api.Request(ctx, method, params)
notAuthorized := isNotAuthorized(err)
if err == zabbixapi.ErrNotAuthenticated || notAuthorized {
isTokenAuth := zabbix.settings.AuthType == settings.AuthTypeToken
if err == zabbixapi.ErrNotAuthenticated || (notAuthorized && !isTokenAuth) {
if notAuthorized {
zabbix.logger.Debug("Authentication token expired, performing re-login")
}
err = zabbix.Login(ctx)
err = zabbix.Authenticate(ctx)
if err != nil {
return nil, err
}
Expand All @@ -106,12 +111,27 @@ func (zabbix *Zabbix) request(ctx context.Context, method string, params ZabbixA
return result, err
}

func (zabbix *Zabbix) Login(ctx context.Context) error {
func (zabbix *Zabbix) Authenticate(ctx context.Context) error {
jsonData, err := simplejson.NewJson(zabbix.dsInfo.JSONData)
if err != nil {
return err
}

authType := zabbix.settings.AuthType
if authType == settings.AuthTypeToken {
token, exists := zabbix.dsInfo.DecryptedSecureJSONData["apiToken"]
if !exists {
return errors.New("cannot find Zabbix API token")
}
err = zabbix.api.AuthenticateWithToken(ctx, token)
if err != nil {
zabbix.logger.Error("Zabbix authentication error", "error", err)
return err
}
zabbix.logger.Debug("Using API token for authentication")
return nil
}

zabbixLogin := jsonData.Get("username").MustString()
var zabbixPassword string
if securePassword, exists := zabbix.dsInfo.DecryptedSecureJSONData["password"]; exists {
Expand Down
7 changes: 4 additions & 3 deletions pkg/zabbix/zabbix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import (
"context"
"testing"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/assert"

"github.com/grafana/grafana-plugin-sdk-go/backend"
)

var basicDatasourceInfo = &backend.DataSourceInstanceSettings{
Expand All @@ -19,15 +20,15 @@ var emptyParams = map[string]interface{}{}

func TestLogin(t *testing.T) {
zabbixClient, _ := MockZabbixClient(basicDatasourceInfo, `{"result":"secretauth"}`, 200)
err := zabbixClient.Login(context.Background())
err := zabbixClient.Authenticate(context.Background())

assert.NoError(t, err)
assert.Equal(t, "secretauth", zabbixClient.api.GetAuth())
}

func TestLoginError(t *testing.T) {
zabbixClient, _ := MockZabbixClient(basicDatasourceInfo, `{"result":""}`, 500)
err := zabbixClient.Login(context.Background())
err := zabbixClient.Authenticate(context.Background())

assert.Error(t, err)
assert.Equal(t, "", zabbixClient.api.GetAuth())
Expand Down
12 changes: 11 additions & 1 deletion pkg/zabbixapi/zabbix_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (

"github.com/alexanderzobnin/grafana-zabbix/pkg/metrics"
"github.com/bitly/go-simplejson"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"golang.org/x/net/context/ctxhttp"

"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

var (
Expand Down Expand Up @@ -168,6 +169,15 @@ func (api *ZabbixAPI) Authenticate(ctx context.Context, username string, passwor
return nil
}

// AuthenticateWithToken performs authentication with API token.
func (api *ZabbixAPI) AuthenticateWithToken(ctx context.Context, token string) error {
if token == "" {
return errors.New("API token is empty")
}
api.SetAuth(token)
return nil
}

func isDeprecatedUserParamError(err error) bool {
if err == nil {
return false
Expand Down
Loading

0 comments on commit ac97694

Please sign in to comment.