mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Added basic auth to data source edit/create, add support for basic auth in data source proxy code, Closes #1510
This commit is contained in:
parent
ea1164322b
commit
9710771f16
@ -73,7 +73,10 @@ func Register(r *macaron.Macaron) {
|
|||||||
|
|
||||||
// Data sources
|
// Data sources
|
||||||
r.Group("/datasources", func() {
|
r.Group("/datasources", func() {
|
||||||
r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
|
r.Combo("/").
|
||||||
|
Get(GetDataSources).
|
||||||
|
Put(bind(m.AddDataSourceCommand{}), AddDataSource).
|
||||||
|
Post(bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
|
||||||
r.Delete("/:id", DeleteDataSource)
|
r.Delete("/:id", DeleteDataSource)
|
||||||
r.Get("/:id", GetDataSourceById)
|
r.Get("/:id", GetDataSourceById)
|
||||||
r.Get("/plugins", GetDataSourcePlugins)
|
r.Get("/plugins", GetDataSourcePlugins)
|
||||||
|
@ -35,6 +35,10 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
|
|||||||
} else {
|
} else {
|
||||||
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
|
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ds.BasicAuth {
|
||||||
|
req.Header.Add("Authorization", util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &httputil.ReverseProxy{Director: director}
|
return &httputil.ReverseProxy{Director: director}
|
||||||
|
@ -50,18 +50,20 @@ func GetDataSourceById(c *middleware.Context) {
|
|||||||
ds := query.Result
|
ds := query.Result
|
||||||
|
|
||||||
c.JSON(200, &dtos.DataSource{
|
c.JSON(200, &dtos.DataSource{
|
||||||
Id: ds.Id,
|
Id: ds.Id,
|
||||||
OrgId: ds.OrgId,
|
OrgId: ds.OrgId,
|
||||||
Name: ds.Name,
|
Name: ds.Name,
|
||||||
Url: ds.Url,
|
Url: ds.Url,
|
||||||
Type: ds.Type,
|
Type: ds.Type,
|
||||||
Access: ds.Access,
|
Access: ds.Access,
|
||||||
Password: ds.Password,
|
Password: ds.Password,
|
||||||
Database: ds.Database,
|
Database: ds.Database,
|
||||||
User: ds.User,
|
User: ds.User,
|
||||||
BasicAuth: ds.BasicAuth,
|
BasicAuth: ds.BasicAuth,
|
||||||
IsDefault: ds.IsDefault,
|
BasicAuthUser: ds.BasicAuthUser,
|
||||||
JsonData: ds.JsonData,
|
BasicAuthPassword: ds.BasicAuthPassword,
|
||||||
|
IsDefault: ds.IsDefault,
|
||||||
|
JsonData: ds.JsonData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,14 +86,7 @@ func DeleteDataSource(c *middleware.Context) {
|
|||||||
c.JsonOK("Data source deleted")
|
c.JsonOK("Data source deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddDataSource(c *middleware.Context) {
|
func AddDataSource(c *middleware.Context, cmd m.AddDataSourceCommand) {
|
||||||
cmd := m.AddDataSourceCommand{}
|
|
||||||
|
|
||||||
if !c.JsonBody(&cmd) {
|
|
||||||
c.JsonApiErr(400, "Validation failed", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.OrgId = c.OrgId
|
cmd.OrgId = c.OrgId
|
||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
@ -38,18 +38,20 @@ type Dashboard struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DataSource struct {
|
type DataSource struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
OrgId int64 `json:"orgId"`
|
OrgId int64 `json:"orgId"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Access m.DsAccess `json:"access"`
|
Access m.DsAccess `json:"access"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Database string `json:"database"`
|
Database string `json:"database"`
|
||||||
BasicAuth bool `json:"basicAuth"`
|
BasicAuth bool `json:"basicAuth"`
|
||||||
IsDefault bool `json:"isDefault"`
|
BasicAuthUser string `json:"basicAuthUser"`
|
||||||
JsonData map[string]interface{} `json:"jsonData"`
|
BasicAuthPassword string `json:"basicAuthPassword"`
|
||||||
|
IsDefault bool `json:"isDefault"`
|
||||||
|
JsonData map[string]interface{} `json:"jsonData"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetricQueryResultDto struct {
|
type MetricQueryResultDto struct {
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, error) {
|
func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, error) {
|
||||||
@ -53,16 +54,18 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
|||||||
defaultDatasource = ds.Name
|
defaultDatasource = ds.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if ds.Type == m.DS_INFLUXDB_08 {
|
if ds.Access == m.DS_ACCESS_DIRECT {
|
||||||
if ds.Access == m.DS_ACCESS_DIRECT {
|
if ds.BasicAuth {
|
||||||
|
dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds.Type == m.DS_INFLUXDB_08 {
|
||||||
dsMap["username"] = ds.User
|
dsMap["username"] = ds.User
|
||||||
dsMap["password"] = ds.Password
|
dsMap["password"] = ds.Password
|
||||||
dsMap["url"] = url + "/db/" + ds.Database
|
dsMap["url"] = url + "/db/" + ds.Database
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ds.Type == m.DS_INFLUXDB {
|
if ds.Type == m.DS_INFLUXDB {
|
||||||
if ds.Access == m.DS_ACCESS_DIRECT {
|
|
||||||
dsMap["username"] = ds.User
|
dsMap["username"] = ds.User
|
||||||
dsMap["password"] = ds.Password
|
dsMap["password"] = ds.Password
|
||||||
dsMap["database"] = ds.Database
|
dsMap["database"] = ds.Database
|
||||||
|
@ -49,31 +49,39 @@ type DataSource struct {
|
|||||||
|
|
||||||
// Also acts as api DTO
|
// Also acts as api DTO
|
||||||
type AddDataSourceCommand struct {
|
type AddDataSourceCommand struct {
|
||||||
OrgId int64 `json:"-"`
|
Name string `json:"name" binding:"Required"`
|
||||||
Name string
|
Type string `json:"type" binding:"Required"`
|
||||||
Type string
|
Access DsAccess `json:"access" binding:"Required"`
|
||||||
Access DsAccess
|
Url string `json:"url"`
|
||||||
Url string
|
Password string `json:"password"`
|
||||||
Password string
|
Database string `json:"database"`
|
||||||
Database string
|
User string `json:"user"`
|
||||||
User string
|
BasicAuth bool `json:"basicAuth"`
|
||||||
IsDefault bool
|
BasicAuthUser string `json:"basicAuthUser"`
|
||||||
|
BasicAuthPassword string `json:"basicAuthPassword"`
|
||||||
|
IsDefault bool `json:"isDefault"`
|
||||||
|
JsonData map[string]interface{} `json:"jsonData"`
|
||||||
|
|
||||||
|
OrgId int64 `json:"-"`
|
||||||
|
|
||||||
Result *DataSource
|
Result *DataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also acts as api DTO
|
// Also acts as api DTO
|
||||||
type UpdateDataSourceCommand struct {
|
type UpdateDataSourceCommand struct {
|
||||||
Id int64 `json:"id" binding:"Required"`
|
Id int64 `json:"id" binding:"Required"`
|
||||||
Name string `json:"name" binding:"Required"`
|
Name string `json:"name" binding:"Required"`
|
||||||
Type string `json:"type" binding:"Required"`
|
Type string `json:"type" binding:"Required"`
|
||||||
Access DsAccess `json:"access" binding:"Required"`
|
Access DsAccess `json:"access" binding:"Required"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Database string `json:"database"`
|
Database string `json:"database"`
|
||||||
IsDefault bool `json:"isDefault"`
|
BasicAuth bool `json:"basicAuth"`
|
||||||
JsonData map[string]interface{} `json:"jsonData"`
|
BasicAuthUser string `json:"basicAuthUser"`
|
||||||
|
BasicAuthPassword string `json:"basicAuthPassword"`
|
||||||
|
IsDefault bool `json:"isDefault"`
|
||||||
|
JsonData map[string]interface{} `json:"jsonData"`
|
||||||
|
|
||||||
OrgId int64 `json:"-"`
|
OrgId int64 `json:"-"`
|
||||||
}
|
}
|
||||||
|
@ -57,17 +57,21 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
|
|||||||
|
|
||||||
return inTransaction(func(sess *xorm.Session) error {
|
return inTransaction(func(sess *xorm.Session) error {
|
||||||
ds := &m.DataSource{
|
ds := &m.DataSource{
|
||||||
OrgId: cmd.OrgId,
|
OrgId: cmd.OrgId,
|
||||||
Name: cmd.Name,
|
Name: cmd.Name,
|
||||||
Type: cmd.Type,
|
Type: cmd.Type,
|
||||||
Access: cmd.Access,
|
Access: cmd.Access,
|
||||||
Url: cmd.Url,
|
Url: cmd.Url,
|
||||||
User: cmd.User,
|
User: cmd.User,
|
||||||
Password: cmd.Password,
|
Password: cmd.Password,
|
||||||
Database: cmd.Database,
|
Database: cmd.Database,
|
||||||
IsDefault: cmd.IsDefault,
|
IsDefault: cmd.IsDefault,
|
||||||
Created: time.Now(),
|
BasicAuth: cmd.BasicAuth,
|
||||||
Updated: time.Now(),
|
BasicAuthUser: cmd.BasicAuthUser,
|
||||||
|
BasicAuthPassword: cmd.BasicAuthPassword,
|
||||||
|
JsonData: cmd.JsonData,
|
||||||
|
Created: time.Now(),
|
||||||
|
Updated: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sess.Insert(ds); err != nil {
|
if _, err := sess.Insert(ds); err != nil {
|
||||||
@ -97,21 +101,25 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
|
|||||||
|
|
||||||
return inTransaction(func(sess *xorm.Session) error {
|
return inTransaction(func(sess *xorm.Session) error {
|
||||||
ds := &m.DataSource{
|
ds := &m.DataSource{
|
||||||
Id: cmd.Id,
|
Id: cmd.Id,
|
||||||
OrgId: cmd.OrgId,
|
OrgId: cmd.OrgId,
|
||||||
Name: cmd.Name,
|
Name: cmd.Name,
|
||||||
Type: cmd.Type,
|
Type: cmd.Type,
|
||||||
Access: cmd.Access,
|
Access: cmd.Access,
|
||||||
Url: cmd.Url,
|
Url: cmd.Url,
|
||||||
User: cmd.User,
|
User: cmd.User,
|
||||||
Password: cmd.Password,
|
Password: cmd.Password,
|
||||||
Database: cmd.Database,
|
Database: cmd.Database,
|
||||||
IsDefault: cmd.IsDefault,
|
IsDefault: cmd.IsDefault,
|
||||||
JsonData: cmd.JsonData,
|
BasicAuth: cmd.BasicAuth,
|
||||||
Updated: time.Now(),
|
BasicAuthUser: cmd.BasicAuthUser,
|
||||||
|
BasicAuthPassword: cmd.BasicAuthPassword,
|
||||||
|
JsonData: cmd.JsonData,
|
||||||
|
Updated: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
sess.UseBool("is_default")
|
sess.UseBool("is_default")
|
||||||
|
sess.UseBool("basic_auth")
|
||||||
|
|
||||||
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
|
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
@ -74,3 +75,8 @@ func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte
|
|||||||
}
|
}
|
||||||
return dk[:keyLen]
|
return dk[:keyLen]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetBasicAuthHeader(user string, password string) string {
|
||||||
|
var userAndPass = user + ":" + password
|
||||||
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(userAndPass))
|
||||||
|
}
|
||||||
|
16
pkg/util/encoding_test.go
Normal file
16
pkg/util/encoding_test.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncoding(t *testing.T) {
|
||||||
|
|
||||||
|
Convey("When generating base64 header", t, func() {
|
||||||
|
result := GetBasicAuthHeader("grafana", "1234")
|
||||||
|
|
||||||
|
So(result, ShouldEqual, "Z3JhZmFuYToxMjM0")
|
||||||
|
})
|
||||||
|
}
|
@ -41,7 +41,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tight-form last">
|
|
||||||
|
<h5>Http settings</h5>
|
||||||
|
<div class="tight-form">
|
||||||
<ul class="tight-form-list">
|
<ul class="tight-form-list">
|
||||||
<li class="tight-form-item" style="width: 80px">
|
<li class="tight-form-item" style="width: 80px">
|
||||||
Url
|
Url
|
||||||
@ -58,6 +60,31 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tight-form">
|
||||||
|
<ul class="tight-form-list">
|
||||||
|
<li class="tight-form-item" style="width: 80px">
|
||||||
|
Basic Auth
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item">
|
||||||
|
Enable
|
||||||
|
<input class="cr1" id="current.basicAuth" type="checkbox" ng-model="current.basicAuth" ng-checked="current.basicAuth">
|
||||||
|
<label for="current.basicAuth" class="cr1"></label>
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item" ng-if="current.basicAuth">
|
||||||
|
User
|
||||||
|
</li>
|
||||||
|
<li ng-if="current.basicAuth">
|
||||||
|
<input type="text" class="tight-form-input input-medium" style="width: 139px" ng-model='current.basicAuthUser' placeholder="user" required></input>
|
||||||
|
</li>
|
||||||
|
<li class="tight-form-item" style="width: 67px" ng-if="current.basicAuth">
|
||||||
|
Password
|
||||||
|
</li>
|
||||||
|
<li ng-if="current.basicAuth">
|
||||||
|
<input type="password" class="tight-form-input input-medium" ng-model='current.basicAuthPassword' placeholder="password" required></input>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div ng-include="datasourceMeta.partials.config" ng-if="datasourceMeta.partials.config"></div>
|
<div ng-include="datasourceMeta.partials.config" ng-if="datasourceMeta.partials.config"></div>
|
||||||
<br>
|
<br>
|
||||||
|
@ -34,7 +34,7 @@ function (angular, _, config, kbn, moment) {
|
|||||||
if (this.basicAuth) {
|
if (this.basicAuth) {
|
||||||
options.withCredentials = true;
|
options.withCredentials = true;
|
||||||
options.headers = {
|
options.headers = {
|
||||||
"Authorization": "Basic " + this.basicAuth
|
"Authorization": this.basicAuth
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ function (angular, _, $, config, kbn, moment) {
|
|||||||
}
|
}
|
||||||
if (this.basicAuth) {
|
if (this.basicAuth) {
|
||||||
options.headers = options.headers || {};
|
options.headers = options.headers || {};
|
||||||
options.headers.Authorization = 'Basic ' + this.basicAuth;
|
options.headers.Authorization = this.basicAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.url = this.url + options.url;
|
options.url = this.url + options.url;
|
||||||
|
@ -181,7 +181,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
|||||||
|
|
||||||
options.headers = options.headers || {};
|
options.headers = options.headers || {};
|
||||||
if (self.basicAuth) {
|
if (self.basicAuth) {
|
||||||
options.headers.Authorization = 'Basic ' + self.basicAuth;
|
options.headers.Authorization = self.basicAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $http(options).success(function (data) {
|
return $http(options).success(function (data) {
|
||||||
|
Loading…
Reference in New Issue
Block a user