diff --git a/grafana b/grafana index d2f21bc93e9..11b74baf792 160000 --- a/grafana +++ b/grafana @@ -1 +1 @@ -Subproject commit d2f21bc93e96d9ac36b1925c0b0b137a7edc94d2 +Subproject commit 11b74baf7920bcd4e39b5e77bfb49e6b08752dc2 diff --git a/pkg/api/api.go b/pkg/api/api.go index 8b1d641a0ed..0142298b38a 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -58,13 +58,13 @@ func Register(r *macaron.Macaron) { r.Delete("/users/:id", RemoveAccountUser) }, reqAccountAdmin) - // Token - r.Group("/tokens", func() { + // auth api keys + r.Group("/auth/keys", func() { r.Combo("/"). - Get(GetTokens). - Post(bind(m.AddTokenCommand{}), AddToken). - Put(bind(m.UpdateTokenCommand{}), UpdateToken) - r.Delete("/:id", DeleteToken) + Get(GetApiKeys). + Post(bind(m.AddApiKeyCommand{}), AddApiKey). + Put(bind(m.UpdateApiKeyCommand{}), UpdateApiKey) + r.Delete("/:id", DeleteApiKey) }, reqAccountAdmin) // Data sources diff --git a/pkg/api/apikey.go b/pkg/api/apikey.go new file mode 100644 index 00000000000..f5dca4fc450 --- /dev/null +++ b/pkg/api/apikey.go @@ -0,0 +1,83 @@ +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 GetApiKeys(c *middleware.Context) { + query := m.GetApiKeysQuery{AccountId: c.AccountId} + + if err := bus.Dispatch(&query); err != nil { + c.JsonApiErr(500, "Failed to list api keys", err) + return + } + + result := make([]*m.ApiKeyDTO, len(query.Result)) + for i, t := range query.Result { + result[i] = &m.ApiKeyDTO{ + Id: t.Id, + Name: t.Name, + Role: t.Role, + Key: t.Key, + } + } + c.JSON(200, result) +} + +func DeleteApiKey(c *middleware.Context) { + id := c.ParamsInt64(":id") + + cmd := &m.DeleteApiKeyCommand{Id: id, AccountId: c.AccountId} + + err := bus.Dispatch(cmd) + if err != nil { + c.JsonApiErr(500, "Failed to delete API key", err) + return + } + + c.JsonOK("API key deleted") +} + +func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) { + if !cmd.Role.IsValid() { + c.JsonApiErr(400, "Invalid role specified", nil) + return + } + + cmd.AccountId = c.AccountId + cmd.Key = util.GetRandomString(64) + + if err := bus.Dispatch(&cmd); err != nil { + c.JsonApiErr(500, "Failed to add API key", err) + return + } + + result := &m.ApiKeyDTO{ + Id: cmd.Result.Id, + Name: cmd.Result.Name, + Role: cmd.Result.Role, + Key: cmd.Result.Key, + } + + c.JSON(200, result) +} + +func UpdateApiKey(c *middleware.Context, cmd m.UpdateApiKeyCommand) { + if !cmd.Role.IsValid() { + c.JsonApiErr(400, "Invalid role specified", nil) + return + } + + cmd.AccountId = c.AccountId + + err := bus.Dispatch(&cmd) + if err != nil { + c.JsonApiErr(500, "Failed to update api key", err) + return + } + + c.JsonOK("API key updated") +} diff --git a/pkg/api/token.go b/pkg/api/token.go deleted file mode 100644 index 6f47aa69827..00000000000 --- a/pkg/api/token.go +++ /dev/null @@ -1,83 +0,0 @@ -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.AccountId} - - if err := bus.Dispatch(&query); 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.AccountId} - - 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 !cmd.Role.IsValid() { - c.JsonApiErr(400, "Invalid role specified", nil) - return - } - - cmd.AccountId = c.AccountId - 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 !cmd.Role.IsValid() { - c.JsonApiErr(400, "Invalid role specified", nil) - return - } - - cmd.AccountId = c.AccountId - - 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 af1d9ba4792..b7ff7459aa6 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -31,12 +31,12 @@ func getRequestUserId(c *Context) int64 { return 0 } -func getApiToken(c *Context) string { +func getApiKey(c *Context) string { header := c.Req.Header.Get("Authorization") parts := strings.SplitN(header, " ", 2) if len(parts) == 2 || parts[0] == "Bearer" { - token := parts[1] - return token + key := parts[1] + return key } return "" diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index f50ef3fc18a..f8f5700cb18 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -39,22 +39,22 @@ func GetContextHandler() macaron.Handler { ctx.IsSignedIn = true ctx.SignedInUser = query.Result } - } else if token := getApiToken(ctx); token != "" { + } else if key := getApiKey(ctx); key != "" { // Try API Key auth - tokenQuery := m.GetTokenByTokenQuery{Token: token} - if err := bus.Dispatch(&tokenQuery); err != nil { - ctx.JsonApiErr(401, "Invalid token", err) + keyQuery := m.GetApiKeyByKeyQuery{Key: key} + if err := bus.Dispatch(&keyQuery); err != nil { + ctx.JsonApiErr(401, "Invalid API key", err) return } else { - tokenInfo := tokenQuery.Result + keyInfo := keyQuery.Result ctx.IsSignedIn = true ctx.SignedInUser = &m.SignedInUser{} // TODO: fix this - ctx.AccountRole = tokenInfo.Role - ctx.ApiKeyId = tokenInfo.Id - ctx.AccountId = tokenInfo.AccountId + ctx.AccountRole = keyInfo.Role + ctx.ApiKeyId = keyInfo.Id + ctx.AccountId = keyInfo.AccountId } } diff --git a/pkg/models/apikey.go b/pkg/models/apikey.go new file mode 100644 index 00000000000..f869f9c87e4 --- /dev/null +++ b/pkg/models/apikey.go @@ -0,0 +1,65 @@ +package models + +import ( + "errors" + "time" +) + +var ErrInvalidApiKey = errors.New("Invalid API Key") + +type ApiKey struct { + Id int64 + AccountId int64 + Name string + Key string + Role RoleType + Created time.Time + Updated time.Time +} + +// --------------------- +// COMMANDS +type AddApiKeyCommand struct { + Name string `json:"name" binding:"required"` + Role RoleType `json:"role" binding:"required"` + AccountId int64 `json:"-"` + Key string `json:"-"` + + Result *ApiKey `json:"-"` +} + +type UpdateApiKeyCommand struct { + Id int64 `json:"id"` + Name string `json:"name"` + Role RoleType `json:"role"` + + AccountId int64 `json:"-"` +} + +type DeleteApiKeyCommand struct { + Id int64 `json:"id"` + AccountId int64 `json:"-"` +} + +// ---------------------- +// QUERIES + +type GetApiKeysQuery struct { + AccountId int64 + Result []*ApiKey +} + +type GetApiKeyByKeyQuery struct { + Key string + Result *ApiKey +} + +// ------------------------ +// DTO & Projections + +type ApiKeyDTO struct { + Id int64 `json:"id"` + Name string `json:"name"` + Key string `json:"key"` + Role RoleType `json:"role"` +} diff --git a/pkg/models/token.go b/pkg/models/token.go deleted file mode 100644 index 752c324f08c..00000000000 --- a/pkg/models/token.go +++ /dev/null @@ -1,66 +0,0 @@ -package models - -import ( - "errors" - "time" -) - -var ErrInvalidToken = errors.New("Invalid token") - -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"` - Role RoleType `json:"role"` - - AccountId int64 `json:"-"` - 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 GetTokenByTokenQuery struct { - Token string - Result *Token -} - -// ------------------------ -// 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/services/sqlstore/tokens.go b/pkg/services/sqlstore/apikey.go similarity index 59% rename from pkg/services/sqlstore/tokens.go rename to pkg/services/sqlstore/apikey.go index 0282bad5df9..bbedaa5dcb2 100644 --- a/pkg/services/sqlstore/tokens.go +++ b/pkg/services/sqlstore/apikey.go @@ -9,35 +9,35 @@ import ( ) func init() { - bus.AddHandler("sql", GetTokens) - bus.AddHandler("sql", GetTokenByToken) - bus.AddHandler("sql", UpdateToken) - bus.AddHandler("sql", DeleteToken) - bus.AddHandler("sql", AddToken) + bus.AddHandler("sql", GetApiKeys) + bus.AddHandler("sql", GetApiKeyByKey) + bus.AddHandler("sql", UpdateApiKey) + bus.AddHandler("sql", DeleteApiKey) + bus.AddHandler("sql", AddApiKey) } -func GetTokens(query *m.GetTokensQuery) error { +func GetApiKeys(query *m.GetApiKeysQuery) error { sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name") - query.Result = make([]*m.Token, 0) + query.Result = make([]*m.ApiKey, 0) return sess.Find(&query.Result) } -func DeleteToken(cmd *m.DeleteTokenCommand) error { +func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error { return inTransaction(func(sess *xorm.Session) error { - var rawSql = "DELETE FROM token WHERE id=? and account_id=?" + var rawSql = "DELETE FROM api_key WHERE id=? and account_id=?" _, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId) return err }) } -func AddToken(cmd *m.AddTokenCommand) error { +func AddApiKey(cmd *m.AddApiKeyCommand) error { return inTransaction(func(sess *xorm.Session) error { - t := m.Token{ + t := m.ApiKey{ AccountId: cmd.AccountId, Name: cmd.Name, Role: cmd.Role, - Token: cmd.Token, + Key: cmd.Key, Created: time.Now(), Updated: time.Now(), } @@ -50,32 +50,30 @@ func AddToken(cmd *m.AddTokenCommand) error { }) } -func UpdateToken(cmd *m.UpdateTokenCommand) error { - +func UpdateApiKey(cmd *m.UpdateApiKeyCommand) error { return inTransaction(func(sess *xorm.Session) error { - t := m.Token{ + t := m.ApiKey{ 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 }) } -func GetTokenByToken(query *m.GetTokenByTokenQuery) error { - var token m.Token - has, err := x.Where("token=?", query.Token).Get(&token) +func GetApiKeyByKey(query *m.GetApiKeyByKeyQuery) error { + var apikey m.ApiKey + has, err := x.Where("key=?", query.Key).Get(&apikey) if err != nil { return err } else if has == false { - return m.ErrInvalidToken + return m.ErrInvalidApiKey } - query.Result = &token + query.Result = &apikey return nil } diff --git a/pkg/services/sqlstore/migrations.go b/pkg/services/sqlstore/migrations.go index d0458fb01f5..8f87992e73f 100644 --- a/pkg/services/sqlstore/migrations.go +++ b/pkg/services/sqlstore/migrations.go @@ -2,13 +2,19 @@ package sqlstore import . "github.com/torkelo/grafana-pro/pkg/services/sqlstore/migrator" +// --- Migration Guide line --- +// 1. Never change a migration that is committed and pushed to master +// 2. Always add new migrations (to change or undo previous migrations) +// 3. Some migraitons are not yet written (rename column, table, drop table, index etc) +// 4 + func addMigrations(mg *Migrator) { addMigrationLogMigrations(mg) addUserMigrations(mg) addAccountMigrations(mg) addDashboardMigration(mg) addDataSourceMigration(mg) - addTokenMigrations(mg) + addApiKeyMigrations(mg) } func addMigrationLogMigrations(mg *Migrator) { @@ -131,19 +137,22 @@ func addDataSourceMigration(mg *Migrator) { Table("data_source").Columns("account_id", "name").Unique()) } -func addTokenMigrations(mg *Migrator) { - mg.AddMigration("create token table", new(AddTableMigration). - Name("token").WithColumns( +func addApiKeyMigrations(mg *Migrator) { + mg.AddMigration("create api_key table", new(AddTableMigration). + Name("api_key").WithColumns( &Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, &Column{Name: "account_id", Type: DB_BigInt, Nullable: false}, &Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false}, - &Column{Name: "token", Type: DB_NVarchar, Length: 255, Nullable: false}, + &Column{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false}, &Column{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false}, &Column{Name: "created", Type: DB_DateTime, Nullable: false}, &Column{Name: "updated", Type: DB_DateTime, Nullable: false}, )) //------- indexes ------------------ - mg.AddMigration("add index token.account_id", new(AddIndexMigration). - Table("token").Columns("account_id")) + mg.AddMigration("add index api_key.account_id", new(AddIndexMigration). + Table("api_key").Columns("account_id")) + + mg.AddMigration("add index api_key.key", new(AddIndexMigration). + Table("api_key").Columns("key").Unique()) }