mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 18:44:54 +08:00
add Token authentication support
Added CRUD methods for Tokens. Extend Auth Handler to check for the presence of a Bearer Authorization header to authenticate against. If there is no header, or the token is not valid, the Auth Handler falls back to looking for a Session.
This commit is contained in:
@ -26,6 +26,12 @@ func Register(m *macaron.Macaron) {
|
|||||||
m.Post("/api/account/using/:id", auth, SetUsingAccount)
|
m.Post("/api/account/using/:id", auth, SetUsingAccount)
|
||||||
m.Get("/api/account/others", auth, GetOtherAccounts)
|
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
|
// data sources
|
||||||
m.Get("/acount/datasources/", auth, Index)
|
m.Get("/acount/datasources/", auth, Index)
|
||||||
m.Get("/api/datasources/list", auth, GetDataSources)
|
m.Get("/api/datasources/list", auth, GetDataSources)
|
||||||
|
90
pkg/api/token.go
Normal file
90
pkg/api/token.go
Normal file
@ -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")
|
||||||
|
}
|
@ -2,10 +2,10 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Unknwon/macaron"
|
"github.com/Unknwon/macaron"
|
||||||
"github.com/macaron-contrib/session"
|
"github.com/macaron-contrib/session"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/torkelo/grafana-pro/pkg/bus"
|
"github.com/torkelo/grafana-pro/pkg/bus"
|
||||||
m "github.com/torkelo/grafana-pro/pkg/models"
|
m "github.com/torkelo/grafana-pro/pkg/models"
|
||||||
@ -39,30 +39,60 @@ func authDenied(c *Context) {
|
|||||||
c.Redirect(setting.AppSubUrl + "/login")
|
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 {
|
func Auth() macaron.Handler {
|
||||||
return func(c *Context, sess session.Store) {
|
return func(c *Context, sess session.Store) {
|
||||||
accountId, err := authGetRequestAccountId(c, sess)
|
authByToken(c)
|
||||||
|
if c.UserAccount == nil {
|
||||||
if err != nil && c.Req.URL.Path != "/login" {
|
authBySession(c, sess)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,23 +23,21 @@ type Account struct {
|
|||||||
Company string
|
Company string
|
||||||
NextDashboardId int
|
NextDashboardId int
|
||||||
UsingAccountId int64
|
UsingAccountId int64
|
||||||
|
Created time.Time
|
||||||
Created time.Time
|
Updated time.Time
|
||||||
Updated time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------
|
// ---------------------
|
||||||
// COMMANDS
|
// COMMANDS
|
||||||
|
|
||||||
type CreateAccountCommand struct {
|
type CreateAccountCommand struct {
|
||||||
Email string `json:"email" binding:"required"`
|
Email string `json:"email" binding:"required"`
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
Password string `json:"password" binding:"required"`
|
Password string `json:"password" binding:"required"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Company string `json:"company"`
|
Company string `json:"company"`
|
||||||
Salt string `json:"-"`
|
Salt string `json:"-"`
|
||||||
|
Result Account `json:"-"`
|
||||||
Result Account `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetUsingAccountCommand struct {
|
type SetUsingAccountCommand struct {
|
||||||
|
62
pkg/models/token.go
Normal file
62
pkg/models/token.go
Normal file
@ -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"`
|
||||||
|
}
|
@ -17,6 +17,7 @@ func init() {
|
|||||||
bus.AddHandler("sql", SetUsingAccount)
|
bus.AddHandler("sql", SetUsingAccount)
|
||||||
bus.AddHandler("sql", GetAccountById)
|
bus.AddHandler("sql", GetAccountById)
|
||||||
bus.AddHandler("sql", GetAccountByLogin)
|
bus.AddHandler("sql", GetAccountByLogin)
|
||||||
|
bus.AddHandler("sql", GetAccountByToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateAccount(cmd *m.CreateAccountCommand) error {
|
func CreateAccount(cmd *m.CreateAccountCommand) error {
|
||||||
@ -109,6 +110,27 @@ func GetAccountById(query *m.GetAccountByIdQuery) error {
|
|||||||
return nil
|
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 {
|
func GetAccountByLogin(query *m.GetAccountByLoginQuery) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ func init() {
|
|||||||
tables = make([]interface{}, 0)
|
tables = make([]interface{}, 0)
|
||||||
|
|
||||||
tables = append(tables, new(m.Account), new(m.Dashboard),
|
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() {
|
func Init() {
|
||||||
|
66
pkg/stores/sqlstore/tokens.go
Normal file
66
pkg/stores/sqlstore/tokens.go
Normal file
@ -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
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user