Commit 27b11b1d by Torkel Ödegaard

InfluxDB now works in proxy mode, influxdb username and password is added in the…

InfluxDB now works in proxy mode, influxdb username and password is added in the backend and never exposed to frontend, #8
parent ec98c201
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")
}
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})
}
package api
import (
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/torkelo/grafana-pro/pkg/bus"
"github.com/torkelo/grafana-pro/pkg/middleware"
m "github.com/torkelo/grafana-pro/pkg/models"
)
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
func NewReverseProxy(target *url.URL, proxyPath string) *httputil.ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, proxyPath)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
}
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("*")
url, _ := url.Parse(query.Result.Url)
proxy := NewReverseProxy(url, proxyPath)
proxy.ServeHTTP(c.RW(), c.Req.Request)
}
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")
}
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
}
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"})
}
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("/")
}
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")
}
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)
}
...@@ -2,6 +2,7 @@ package utils ...@@ -2,6 +2,7 @@ package utils
import ( import (
"net/url" "net/url"
"strings"
) )
type UrlQueryReader struct { type UrlQueryReader struct {
...@@ -22,3 +23,15 @@ func (r *UrlQueryReader) Get(name string, def string) string { ...@@ -22,3 +23,15 @@ func (r *UrlQueryReader) Get(name string, def string) string {
return val[0] return val[0]
} }
func JoinUrlFragments(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment