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:
Torkel Ödegaard 2015-03-02 09:58:35 +01:00
parent ea1164322b
commit 9710771f16
13 changed files with 156 additions and 84 deletions

View File

@ -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)

View File

@ -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}

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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:"-"`
} }

View File

@ -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 {

View File

@ -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
View 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")
})
}

View File

@ -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&nbsp;
<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>

View File

@ -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
}; };
} }

View File

@ -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;

View File

@ -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) {