diff --git a/pkg/api/api.go b/pkg/api/api.go index 48180af74dc..d7bcd33869b 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -26,6 +26,12 @@ func Register(m *macaron.Macaron) { m.Post("/api/account/using/:id", auth, SetUsingAccount) m.Get("/api/account/others", auth, GetOtherAccounts) + // Token + m.Get("/api/tokens/list", auth, GetTokens) + m.Put("/api/tokens", auth, AddToken) + m.Post("/api/tokens", auth, UpdateToken) + m.Delete("/api/tokens/:id", auth, DeleteToken) + // data sources m.Get("/acount/datasources/", auth, Index) m.Get("/api/datasources/list", auth, GetDataSources) diff --git a/pkg/api/token.go b/pkg/api/token.go new file mode 100644 index 00000000000..3805ab3e359 --- /dev/null +++ b/pkg/api/token.go @@ -0,0 +1,90 @@ +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/util" +) + +func GetTokens(c *middleware.Context) { + query := m.GetTokensQuery{AccountId: c.Account.Id} + err := bus.Dispatch(&query) + + if err != nil { + c.JsonApiErr(500, "Failed to list tokens", err) + return + } + result := make([]*m.TokenDTO, len(query.Result)) + for i, t := range query.Result { + result[i] = &m.TokenDTO{ + Id: t.Id, + Name: t.Name, + Role: t.Role, + Token: t.Token, + } + } + c.JSON(200, result) +} + +func DeleteToken(c *middleware.Context) { + id := c.ParamsInt64(":id") + + cmd := &m.DeleteTokenCommand{Id: id, AccountId: c.UserAccount.Id} + + err := bus.Dispatch(cmd) + if err != nil { + c.JsonApiErr(500, "Failed to delete token", err) + return + } + + c.JsonOK("Token deleted") +} + +func AddToken(c *middleware.Context) { + cmd := m.AddTokenCommand{} + + if !c.JsonBody(&cmd) { + c.JsonApiErr(400, "Validation failed", nil) + return + } + + if cmd.Role != m.ROLE_READ_WRITE && cmd.Role != m.ROLE_READ { + c.JsonApiErr(400, "Invalid role specified", nil) + return + } + + cmd.AccountId = c.Account.Id + cmd.Token = util.GetRandomString(64) + + if err := bus.Dispatch(&cmd); err != nil { + c.JsonApiErr(500, "Failed to add token", err) + return + } + result := &m.TokenDTO{ + Id: cmd.Result.Id, + Name: cmd.Result.Name, + Role: cmd.Result.Role, + Token: cmd.Result.Token, + } + c.JSON(200, result) +} + +func UpdateToken(c *middleware.Context) { + cmd := m.UpdateTokenCommand{} + + 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 token", err) + return + } + + c.JsonOK("Token updated") +} diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index df4f4fbbc21..2536fb01a6d 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -2,10 +2,10 @@ package middleware import ( "errors" - "strconv" - "github.com/Unknwon/macaron" "github.com/macaron-contrib/session" + "strconv" + "strings" "github.com/torkelo/grafana-pro/pkg/bus" m "github.com/torkelo/grafana-pro/pkg/models" @@ -39,30 +39,60 @@ func authDenied(c *Context) { c.Redirect(setting.AppSubUrl + "/login") } +func authByToken(c *Context) { + header := c.Req.Header.Get("Authorization") + parts := strings.SplitN(header, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return + } + token := parts[1] + userQuery := m.GetAccountByTokenQuery{Token: token} + err := bus.Dispatch(&userQuery) + if err != nil { + return + } + + usingQuery := m.GetAccountByIdQuery{Id: userQuery.Result.UsingAccountId} + err = bus.Dispatch(&usingQuery) + if err != nil { + return + } + + c.UserAccount = userQuery.Result + c.Account = usingQuery.Result +} + +func authBySession(c *Context, sess session.Store) { + accountId, err := authGetRequestAccountId(c, sess) + + if err != nil && c.Req.URL.Path != "/login" { + authDenied(c) + return + } + + userQuery := m.GetAccountByIdQuery{Id: accountId} + err = bus.Dispatch(&userQuery) + if err != nil { + authDenied(c) + return + } + + usingQuery := m.GetAccountByIdQuery{Id: userQuery.Result.UsingAccountId} + err = bus.Dispatch(&usingQuery) + if err != nil { + authDenied(c) + return + } + + c.UserAccount = userQuery.Result + c.Account = usingQuery.Result +} + func Auth() macaron.Handler { return func(c *Context, sess session.Store) { - accountId, err := authGetRequestAccountId(c, sess) - - if err != nil && c.Req.URL.Path != "/login" { - authDenied(c) - return + authByToken(c) + if c.UserAccount == nil { + authBySession(c, sess) } - - userQuery := m.GetAccountByIdQuery{Id: accountId} - err = bus.Dispatch(&userQuery) - if err != nil { - authDenied(c) - return - } - - usingQuery := m.GetAccountByIdQuery{Id: userQuery.Result.UsingAccountId} - err = bus.Dispatch(&usingQuery) - if err != nil { - authDenied(c) - return - } - - c.UserAccount = userQuery.Result - c.Account = usingQuery.Result } } diff --git a/pkg/models/account.go b/pkg/models/account.go index d39de09514a..9ba74f0c7f7 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -23,23 +23,21 @@ type Account struct { Company string NextDashboardId int UsingAccountId int64 - - Created time.Time - Updated time.Time + Created time.Time + Updated time.Time } // --------------------- // COMMANDS type CreateAccountCommand struct { - Email string `json:"email" binding:"required"` - Login string `json:"login"` - Password string `json:"password" binding:"required"` - Name string `json:"name"` - Company string `json:"company"` - Salt string `json:"-"` - - Result Account `json:"-"` + Email string `json:"email" binding:"required"` + Login string `json:"login"` + Password string `json:"password" binding:"required"` + Name string `json:"name"` + Company string `json:"company"` + Salt string `json:"-"` + Result Account `json:"-"` } type SetUsingAccountCommand struct { diff --git a/pkg/models/token.go b/pkg/models/token.go new file mode 100644 index 00000000000..0ba1114026d --- /dev/null +++ b/pkg/models/token.go @@ -0,0 +1,62 @@ +package models + +import ( + "time" +) + +type Token struct { + Id int64 + AccountId int64 `xorm:"not null unique(uix_account_id_name)"` + Name string `xorm:"not null unique(uix_account_id_name)"` + Token string `xorm:"UNIQUE NOT NULL"` + Role RoleType `xorm:"not null"` + Created time.Time + Updated time.Time +} + +// --------------------- +// COMMANDS +type AddTokenCommand struct { + Name string `json:"name" binding:"required"` + Role RoleType `json:"role" binding:"required"` + AccountId int64 `json:"-"` + Token string `json:"-"` + Result *Token `json:"-"` +} + +type UpdateTokenCommand struct { + Id int64 `json:"id"` + Name string `json:"name"` + AccountId int64 `json:"-"` + Role RoleType `json:"role"` + Result *Token `json:"-"` +} + +type DeleteTokenCommand struct { + Id int64 `json:"id"` + AccountId int64 `json:"-"` + Result *Token `json:"-"` +} + +// ---------------------- +// QUERIES + +type GetTokensQuery struct { + AccountId int64 + Result []*Token +} + +type GetAccountByTokenQuery struct { + Token string + Result *Account +} + +// ------------------------ +// DTO & Projections + +type TokenDTO struct { + Id int64 `json:"id"` + Name string `json:"name"` + Token string `json:"token"` + Role RoleType `json:"role"` +} diff --git a/pkg/stores/sqlstore/accounts.go b/pkg/stores/sqlstore/accounts.go index 4bfa3d60c3a..827629a36f6 100644 --- a/pkg/stores/sqlstore/accounts.go +++ b/pkg/stores/sqlstore/accounts.go @@ -17,6 +17,7 @@ func init() { bus.AddHandler("sql", SetUsingAccount) bus.AddHandler("sql", GetAccountById) bus.AddHandler("sql", GetAccountByLogin) + bus.AddHandler("sql", GetAccountByToken) } func CreateAccount(cmd *m.CreateAccountCommand) error { @@ -109,6 +110,27 @@ func GetAccountById(query *m.GetAccountByIdQuery) error { return nil } +func GetAccountByToken(query *m.GetAccountByTokenQuery) error { + var err error + + var account m.Account + has, err := x.Where("token=?", query.Token).Get(&account) + + if err != nil { + return err + } else if has == false { + return m.ErrAccountNotFound + } + + if account.UsingAccountId == 0 { + account.UsingAccountId = account.Id + } + + query.Result = &account + + return nil +} + func GetAccountByLogin(query *m.GetAccountByLoginQuery) error { var err error diff --git a/pkg/stores/sqlstore/sqlstore.go b/pkg/stores/sqlstore/sqlstore.go index b6aefa34ee4..e21a9c2e6e2 100644 --- a/pkg/stores/sqlstore/sqlstore.go +++ b/pkg/stores/sqlstore/sqlstore.go @@ -38,7 +38,8 @@ func init() { tables = make([]interface{}, 0) tables = append(tables, new(m.Account), new(m.Dashboard), - new(m.Collaborator), new(m.DataSource), new(DashboardTag)) + new(m.Collaborator), new(m.DataSource), new(DashboardTag), + new(m.Token)) } func Init() { diff --git a/pkg/stores/sqlstore/tokens.go b/pkg/stores/sqlstore/tokens.go new file mode 100644 index 00000000000..2db57054afc --- /dev/null +++ b/pkg/stores/sqlstore/tokens.go @@ -0,0 +1,66 @@ +package sqlstore + +import ( + "github.com/go-xorm/xorm" + "github.com/torkelo/grafana-pro/pkg/bus" + m "github.com/torkelo/grafana-pro/pkg/models" + "time" +) + +func init() { + bus.AddHandler("sql", GetTokens) + bus.AddHandler("sql", AddToken) + bus.AddHandler("sql", UpdateToken) + bus.AddHandler("sql", DeleteToken) +} + +func GetTokens(query *m.GetTokensQuery) error { + sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name") + + query.Result = make([]*m.Token, 0) + return sess.Find(&query.Result) +} + +func DeleteToken(cmd *m.DeleteTokenCommand) error { + return inTransaction(func(sess *xorm.Session) error { + var rawSql = "DELETE FROM token WHERE id=? and account_id=?" + _, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId) + return err + }) +} + +func AddToken(cmd *m.AddTokenCommand) error { + + return inTransaction(func(sess *xorm.Session) error { + t := m.Token{ + AccountId: cmd.AccountId, + Name: cmd.Name, + Role: cmd.Role, + Token: cmd.Token, + Created: time.Now(), + Updated: time.Now(), + } + + if _, err := sess.Insert(&t); err != nil { + return err + } + cmd.Result = &t + return nil + }) +} + +func UpdateToken(cmd *m.UpdateTokenCommand) error { + + return inTransaction(func(sess *xorm.Session) error { + t := m.Token{ + Id: cmd.Id, + AccountId: cmd.AccountId, + Name: cmd.Name, + Role: cmd.Role, + Updated: time.Now(), + } + + _, err := sess.Where("id=? and account_id=?", t.Id, t.AccountId).Update(&t) + return err + }) +}