mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 02:32:14 +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.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)
|
||||
|
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 (
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
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", 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
|
||||
|
||||
|
@ -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() {
|
||||
|
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