mirror of https://github.com/grafana/grafana
InfluxDB now works in proxy mode, influxdb username and password is added in the backend and never exposed to frontend, #8
parent
27b11b1d79
commit
164d11c816
@ -0,0 +1,123 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
"github.com/torkelo/grafana-pro/pkg/utils" |
||||
) |
||||
|
||||
func GetAccount(c *middleware.Context) { |
||||
query := m.GetAccountInfoQuery{Id: c.UserAccount.Id} |
||||
err := bus.Dispatch(&query) |
||||
|
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to fetch collaboratos", err) |
||||
return |
||||
} |
||||
|
||||
c.JSON(200, query.Result) |
||||
} |
||||
|
||||
func AddCollaborator(c *middleware.Context) { |
||||
var cmd m.AddCollaboratorCommand |
||||
|
||||
if !c.JsonBody(&cmd) { |
||||
c.JsonApiErr(400, "Invalid request", nil) |
||||
return |
||||
} |
||||
|
||||
userQuery := m.GetAccountByLoginQuery{Login: cmd.Email} |
||||
err := bus.Dispatch(&userQuery) |
||||
if err != nil { |
||||
c.JsonApiErr(404, "Collaborator not found", nil) |
||||
return |
||||
} |
||||
|
||||
accountToAdd := userQuery.Result |
||||
|
||||
if accountToAdd.Id == c.UserAccount.Id { |
||||
c.JsonApiErr(400, "Cannot add yourself as collaborator", nil) |
||||
return |
||||
} |
||||
|
||||
cmd.AccountId = accountToAdd.Id |
||||
cmd.ForAccountId = c.UserAccount.Id |
||||
cmd.Role = m.ROLE_READ_WRITE |
||||
|
||||
err = bus.Dispatch(&cmd) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Could not add collaborator", err) |
||||
return |
||||
} |
||||
|
||||
c.JsonOK("Collaborator added") |
||||
} |
||||
|
||||
func GetOtherAccounts(c *middleware.Context) { |
||||
query := m.GetOtherAccountsQuery{AccountId: c.UserAccount.Id} |
||||
err := bus.Dispatch(&query) |
||||
|
||||
if err != nil { |
||||
c.JSON(500, utils.DynMap{"message": err.Error()}) |
||||
return |
||||
} |
||||
|
||||
result := append(query.Result, &m.OtherAccountDTO{ |
||||
Id: c.UserAccount.Id, |
||||
Role: "owner", |
||||
Email: c.UserAccount.Email, |
||||
}) |
||||
|
||||
for _, ac := range result { |
||||
if ac.Id == c.UserAccount.UsingAccountId { |
||||
ac.IsUsing = true |
||||
break |
||||
} |
||||
} |
||||
|
||||
c.JSON(200, result) |
||||
} |
||||
|
||||
func validateUsingAccount(accountId int64, otherId int64) bool { |
||||
if accountId == otherId { |
||||
return true |
||||
} |
||||
|
||||
query := m.GetOtherAccountsQuery{AccountId: accountId} |
||||
err := bus.Dispatch(&query) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
|
||||
// validate that the account id in the list
|
||||
valid := false |
||||
for _, other := range query.Result { |
||||
if other.Id == otherId { |
||||
valid = true |
||||
} |
||||
} |
||||
return valid |
||||
} |
||||
|
||||
func SetUsingAccount(c *middleware.Context) { |
||||
usingAccountId := c.ParamsInt64(":id") |
||||
|
||||
if !validateUsingAccount(c.UserAccount.Id, usingAccountId) { |
||||
c.JsonApiErr(401, "Not a valid account", nil) |
||||
return |
||||
} |
||||
|
||||
cmd := m.SetUsingAccountCommand{ |
||||
AccountId: c.UserAccount.Id, |
||||
UsingAccountId: usingAccountId, |
||||
} |
||||
|
||||
err := bus.Dispatch(&cmd) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to update account", err) |
||||
return |
||||
} |
||||
|
||||
c.JsonOK("Active account changed") |
||||
} |
||||
@ -0,0 +1,73 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
"github.com/torkelo/grafana-pro/pkg/utils" |
||||
) |
||||
|
||||
func GetDashboard(c *middleware.Context) { |
||||
slug := c.Params(":slug") |
||||
|
||||
dash, err := m.GetDashboard(slug, c.GetAccountId()) |
||||
if err != nil { |
||||
c.JsonApiErr(404, "Dashboard not found", nil) |
||||
return |
||||
} |
||||
|
||||
dash.Data["id"] = dash.Id |
||||
|
||||
c.JSON(200, dash.Data) |
||||
} |
||||
|
||||
func DeleteDashboard(c *middleware.Context) { |
||||
slug := c.Params(":slug") |
||||
|
||||
dash, err := m.GetDashboard(slug, c.GetAccountId()) |
||||
if err != nil { |
||||
c.JsonApiErr(404, "Dashboard not found", nil) |
||||
return |
||||
} |
||||
|
||||
err = m.DeleteDashboard(slug, c.GetAccountId()) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to delete dashboard", err) |
||||
return |
||||
} |
||||
|
||||
var resp = map[string]interface{}{"title": dash.Title} |
||||
|
||||
c.JSON(200, resp) |
||||
} |
||||
|
||||
func Search(c *middleware.Context) { |
||||
query := c.Query("q") |
||||
|
||||
results, err := m.SearchQuery(query, c.GetAccountId()) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Search failed", err) |
||||
return |
||||
} |
||||
|
||||
c.JSON(200, results) |
||||
} |
||||
|
||||
func PostDashboard(c *middleware.Context) { |
||||
var cmd m.SaveDashboardCommand |
||||
|
||||
if !c.JsonBody(&cmd) { |
||||
c.JsonApiErr(400, "bad request", nil) |
||||
return |
||||
} |
||||
|
||||
cmd.AccountId = c.GetAccountId() |
||||
|
||||
err := bus.Dispatch(&cmd) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to save dashboard", err) |
||||
return |
||||
} |
||||
|
||||
c.JSON(200, utils.DynMap{"status": "success", "slug": cmd.Result.Slug}) |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/http/httputil" |
||||
"net/url" |
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
"github.com/torkelo/grafana-pro/pkg/utils" |
||||
) |
||||
|
||||
func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy { |
||||
target, _ := url.Parse(ds.Url) |
||||
|
||||
director := func(req *http.Request) { |
||||
req.URL.Scheme = target.Scheme |
||||
req.URL.Host = target.Host |
||||
|
||||
reqQueryVals := req.URL.Query() |
||||
|
||||
if ds.Type == m.DS_INFLUXDB { |
||||
req.URL.Path = utils.JoinUrlFragments(target.Path, "db/"+ds.Database+"/"+proxyPath) |
||||
reqQueryVals.Add("u", ds.User) |
||||
reqQueryVals.Add("p", ds.Password) |
||||
req.URL.RawQuery = reqQueryVals.Encode() |
||||
} else { |
||||
req.URL.Path = utils.JoinUrlFragments(target.Path, proxyPath) |
||||
} |
||||
} |
||||
|
||||
return &httputil.ReverseProxy{Director: director} |
||||
} |
||||
|
||||
// TODO: need to cache datasources
|
||||
func ProxyDataSourceRequest(c *middleware.Context) { |
||||
id := c.ParamsInt64(":id") |
||||
|
||||
query := m.GetDataSourceByIdQuery{ |
||||
Id: id, |
||||
AccountId: c.GetAccountId(), |
||||
} |
||||
|
||||
err := bus.Dispatch(&query) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Unable to load datasource meta data", err) |
||||
} |
||||
|
||||
proxyPath := c.Params("*") |
||||
proxy := NewReverseProxy(&query.Result, proxyPath) |
||||
proxy.ServeHTTP(c.RW(), c.Req.Request) |
||||
} |
||||
@ -0,0 +1,58 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/url" |
||||
"testing" |
||||
|
||||
. "github.com/smartystreets/goconvey/convey" |
||||
|
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
) |
||||
|
||||
func TestAccountDataAccess(t *testing.T) { |
||||
|
||||
Convey("When getting graphite datasource proxy", t, func() { |
||||
ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE} |
||||
proxy := NewReverseProxy(&ds, "/render") |
||||
|
||||
requestUrl, _ := url.Parse("http://grafana.com/sub") |
||||
req := http.Request{URL: requestUrl} |
||||
|
||||
proxy.Director(&req) |
||||
|
||||
Convey("Can translate request url and path", func() { |
||||
So(req.URL.Host, ShouldEqual, "graphite:8080") |
||||
So(req.URL.Path, ShouldEqual, "/render") |
||||
}) |
||||
}) |
||||
|
||||
Convey("When getting influxdb datasource proxy", t, func() { |
||||
ds := m.DataSource{ |
||||
Type: m.DS_INFLUXDB, |
||||
Url: "http://influxdb:8083", |
||||
Database: "site", |
||||
User: "user", |
||||
Password: "password", |
||||
} |
||||
|
||||
proxy := NewReverseProxy(&ds, "") |
||||
|
||||
requestUrl, _ := url.Parse("http://grafana.com/sub") |
||||
req := http.Request{URL: requestUrl} |
||||
|
||||
proxy.Director(&req) |
||||
|
||||
Convey("Should add db to url", func() { |
||||
So(req.URL.Path, ShouldEqual, "/db/site/") |
||||
}) |
||||
|
||||
Convey("Should add username and password", func() { |
||||
queryVals := req.URL.Query() |
||||
So(queryVals["u"][0], ShouldEqual, "user") |
||||
So(queryVals["p"][0], ShouldEqual, "password") |
||||
}) |
||||
|
||||
}) |
||||
|
||||
} |
||||
@ -0,0 +1,93 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"github.com/torkelo/grafana-pro/pkg/api/dtos" |
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
) |
||||
|
||||
func GetDataSources(c *middleware.Context) { |
||||
query := m.GetDataSourcesQuery{AccountId: c.Account.Id} |
||||
err := bus.Dispatch(&query) |
||||
|
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to query datasources", err) |
||||
return |
||||
} |
||||
|
||||
result := make([]*dtos.DataSource, len(query.Result)) |
||||
for i, ds := range query.Result { |
||||
result[i] = &dtos.DataSource{ |
||||
Id: ds.Id, |
||||
AccountId: ds.AccountId, |
||||
Name: ds.Name, |
||||
Url: ds.Url, |
||||
Type: ds.Type, |
||||
Access: ds.Access, |
||||
Password: ds.Password, |
||||
Database: ds.Database, |
||||
User: ds.User, |
||||
BasicAuth: ds.BasicAuth, |
||||
} |
||||
} |
||||
|
||||
c.JSON(200, result) |
||||
} |
||||
|
||||
func DeleteDataSource(c *middleware.Context) { |
||||
id := c.ParamsInt64(":id") |
||||
|
||||
if id <= 0 { |
||||
c.JsonApiErr(400, "Missing valid datasource id", nil) |
||||
return |
||||
} |
||||
|
||||
cmd := &m.DeleteDataSourceCommand{Id: id, AccountId: c.UserAccount.Id} |
||||
|
||||
err := bus.Dispatch(cmd) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to delete datasource", err) |
||||
return |
||||
} |
||||
|
||||
c.JsonOK("Data source deleted") |
||||
} |
||||
|
||||
func AddDataSource(c *middleware.Context) { |
||||
cmd := m.AddDataSourceCommand{} |
||||
|
||||
if !c.JsonBody(&cmd) { |
||||
c.JsonApiErr(400, "Validation failed", nil) |
||||
return |
||||
} |
||||
|
||||
cmd.AccountId = c.Account.Id |
||||
|
||||
err := bus.Dispatch(&cmd) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to add datasource", err) |
||||
return |
||||
} |
||||
|
||||
c.JsonOK("Datasource added") |
||||
} |
||||
|
||||
func UpdateDataSource(c *middleware.Context) { |
||||
cmd := m.UpdateDataSourceCommand{} |
||||
|
||||
if !c.JsonBody(&cmd) { |
||||
c.JsonApiErr(400, "Validation failed", nil) |
||||
return |
||||
} |
||||
|
||||
cmd.AccountId = c.Account.Id |
||||
|
||||
err := bus.Dispatch(&cmd) |
||||
if err != nil { |
||||
c.JsonApiErr(500, "Failed to update datasource", err) |
||||
return |
||||
} |
||||
|
||||
c.JsonOK("Datasource updated") |
||||
} |
||||
@ -0,0 +1,68 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"strconv" |
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
) |
||||
|
||||
func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error) { |
||||
accountDataSources := make([]*m.DataSource, 0) |
||||
|
||||
if c.Account != nil { |
||||
query := m.GetDataSourcesQuery{AccountId: c.Account.Id} |
||||
err := bus.Dispatch(&query) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
accountDataSources = query.Result |
||||
} |
||||
|
||||
datasources := make(map[string]interface{}) |
||||
|
||||
for i, ds := range accountDataSources { |
||||
url := ds.Url |
||||
|
||||
if ds.Access == m.DS_ACCESS_PROXY { |
||||
url = "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10) |
||||
} |
||||
|
||||
var dsMap = map[string]interface{}{ |
||||
"type": ds.Type, |
||||
"url": url, |
||||
} |
||||
|
||||
if ds.Type == m.DS_INFLUXDB { |
||||
if ds.Access == m.DS_ACCESS_DIRECT { |
||||
dsMap["username"] = ds.User |
||||
dsMap["password"] = ds.Password |
||||
dsMap["url"] = url + "/db/" + ds.Database |
||||
} |
||||
} |
||||
|
||||
// temp hack, first is always default
|
||||
// TODO: implement default ds account setting
|
||||
if i == 0 { |
||||
dsMap["default"] = true |
||||
} |
||||
|
||||
datasources[ds.Name] = dsMap |
||||
} |
||||
|
||||
// add grafana backend data source
|
||||
datasources["grafana"] = map[string]interface{}{ |
||||
"type": "grafana", |
||||
"url": "", |
||||
"grafanaDB": true, |
||||
} |
||||
|
||||
jsonObj := map[string]interface{}{ |
||||
"datasources": datasources, |
||||
} |
||||
|
||||
return jsonObj, nil |
||||
} |
||||
@ -0,0 +1,61 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"github.com/torkelo/grafana-pro/pkg/api/dtos" |
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/log" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
"github.com/torkelo/grafana-pro/pkg/utils" |
||||
) |
||||
|
||||
type loginJsonModel struct { |
||||
Email string `json:"email" binding:"required"` |
||||
Password string `json:"password" binding:"required"` |
||||
Remember bool `json:"remember"` |
||||
} |
||||
|
||||
func LoginPost(c *middleware.Context) { |
||||
var loginModel loginJsonModel |
||||
|
||||
if !c.JsonBody(&loginModel) { |
||||
c.JSON(400, utils.DynMap{"status": "bad request"}) |
||||
return |
||||
} |
||||
|
||||
userQuery := m.GetAccountByLoginQuery{Login: loginModel.Email} |
||||
err := bus.Dispatch(&userQuery) |
||||
|
||||
if err != nil { |
||||
c.JsonApiErr(401, "Invalid username or password", err) |
||||
return |
||||
} |
||||
|
||||
account := userQuery.Result |
||||
|
||||
if loginModel.Password != account.Password { |
||||
c.JsonApiErr(401, "Invalid username or password", err) |
||||
return |
||||
} |
||||
|
||||
loginUserWithAccount(account, c) |
||||
|
||||
var resp = &dtos.LoginResult{} |
||||
resp.Status = "Logged in" |
||||
resp.User.Login = account.Login |
||||
|
||||
c.JSON(200, resp) |
||||
} |
||||
|
||||
func loginUserWithAccount(account *m.Account, c *middleware.Context) { |
||||
if account == nil { |
||||
log.Error(3, "Account login with nil account") |
||||
} |
||||
|
||||
c.Session.Set("accountId", account.Id) |
||||
} |
||||
|
||||
func LogoutPost(c *middleware.Context) { |
||||
c.Session.Delete("accountId") |
||||
c.JSON(200, utils.DynMap{"status": "logged out"}) |
||||
} |
||||
@ -0,0 +1,78 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/log" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
"github.com/torkelo/grafana-pro/pkg/setting" |
||||
"github.com/torkelo/grafana-pro/pkg/social" |
||||
) |
||||
|
||||
func OAuthLogin(ctx *middleware.Context) { |
||||
if setting.OAuthService == nil { |
||||
ctx.Handle(404, "login.OAuthLogin(oauth service not enabled)", nil) |
||||
return |
||||
} |
||||
|
||||
name := ctx.Params(":name") |
||||
connect, ok := social.SocialMap[name] |
||||
if !ok { |
||||
ctx.Handle(404, "login.OAuthLogin(social login not enabled)", errors.New(name)) |
||||
return |
||||
} |
||||
|
||||
code := ctx.Query("code") |
||||
if code == "" { |
||||
ctx.Redirect(connect.AuthCodeURL("", "online", "auto")) |
||||
return |
||||
} |
||||
log.Info("code: %v", code) |
||||
|
||||
// handle call back
|
||||
transport, err := connect.NewTransportFromCode(code) |
||||
if err != nil { |
||||
ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err) |
||||
return |
||||
} |
||||
|
||||
log.Trace("login.OAuthLogin(Got token)") |
||||
|
||||
userInfo, err := connect.UserInfo(transport) |
||||
if err != nil { |
||||
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err) |
||||
return |
||||
} |
||||
|
||||
log.Info("login.OAuthLogin(social login): %s", userInfo) |
||||
|
||||
userQuery := m.GetAccountByLoginQuery{Login: userInfo.Email} |
||||
err = bus.Dispatch(&userQuery) |
||||
|
||||
// create account if missing
|
||||
if err == m.ErrAccountNotFound { |
||||
cmd := &m.CreateAccountCommand{ |
||||
Login: userInfo.Email, |
||||
Email: userInfo.Email, |
||||
Name: userInfo.Name, |
||||
Company: userInfo.Company, |
||||
} |
||||
|
||||
if err = bus.Dispatch(&cmd); err != nil { |
||||
ctx.Handle(500, "Failed to create account", err) |
||||
return |
||||
} |
||||
|
||||
userQuery.Result = &cmd.Result |
||||
} else if err != nil { |
||||
ctx.Handle(500, "Unexpected error", err) |
||||
} |
||||
|
||||
// login
|
||||
loginUserWithAccount(userQuery.Result, ctx) |
||||
|
||||
ctx.Redirect("/") |
||||
} |
||||
@ -0,0 +1,26 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"github.com/torkelo/grafana-pro/pkg/bus" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
m "github.com/torkelo/grafana-pro/pkg/models" |
||||
) |
||||
|
||||
func CreateAccount(c *middleware.Context) { |
||||
var cmd m.CreateAccountCommand |
||||
|
||||
if !c.JsonBody(&cmd) { |
||||
c.JsonApiErr(400, "Validation error", nil) |
||||
return |
||||
} |
||||
|
||||
cmd.Login = cmd.Email |
||||
err := bus.Dispatch(&cmd) |
||||
|
||||
if err != nil { |
||||
c.JsonApiErr(500, "failed to create account", err) |
||||
return |
||||
} |
||||
|
||||
c.JsonOK("Account created") |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"net/http" |
||||
"strconv" |
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/components/renderer" |
||||
"github.com/torkelo/grafana-pro/pkg/middleware" |
||||
"github.com/torkelo/grafana-pro/pkg/utils" |
||||
) |
||||
|
||||
func RenderToPng(c *middleware.Context) { |
||||
accountId := c.GetAccountId() |
||||
queryReader := utils.NewUrlQueryReader(c.Req.URL) |
||||
queryParams := "?render&accountId=" + strconv.FormatInt(accountId, 10) + "&" + c.Req.URL.RawQuery |
||||
|
||||
renderOpts := &renderer.RenderOpts{ |
||||
Url: c.Params("*") + queryParams, |
||||
Width: queryReader.Get("width", "800"), |
||||
Height: queryReader.Get("height", "400"), |
||||
} |
||||
|
||||
renderOpts.Url = "http://localhost:3000/" + renderOpts.Url |
||||
|
||||
pngPath, err := renderer.RenderToPng(renderOpts) |
||||
if err != nil { |
||||
c.HTML(500, "error.html", nil) |
||||
} |
||||
|
||||
c.Resp.Header().Set("Content-Type", "image/png") |
||||
http.ServeFile(c.Resp, c.Req.Request, pngPath) |
||||
} |
||||
Loading…
Reference in new issue