diff --git a/pkg/api/apikey.go b/pkg/api/apikey.go index 2ae0a1e89ae..237fdc48ab0 100644 --- a/pkg/api/apikey.go +++ b/pkg/api/apikey.go @@ -1,10 +1,11 @@ package api import ( + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/util" ) func GetApiKeys(c *middleware.Context) { @@ -47,35 +48,19 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) { } cmd.OrgId = c.OrgId - cmd.Key = util.GetRandomString(64) + + newKeyInfo := apikeygen.New(cmd.OrgId, cmd.Name) + cmd.Key = newKeyInfo.HashedKey if err := bus.Dispatch(&cmd); err != nil { c.JsonApiErr(500, "Failed to add API key", err) return } - result := &m.ApiKeyDTO{ - Id: cmd.Result.Id, + result := &dtos.NewApiKeyResult{ Name: cmd.Result.Name, - Role: cmd.Result.Role, + Key: newKeyInfo.ClientSecret, } 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.OrgId = c.OrgId - - 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/dtos/apikey.go b/pkg/api/dtos/apikey.go new file mode 100644 index 00000000000..5b929917f1f --- /dev/null +++ b/pkg/api/dtos/apikey.go @@ -0,0 +1,6 @@ +package dtos + +type NewApiKeyResult struct { + Name string `json:"name"` + Key string `json:"key"` +} diff --git a/pkg/components/apikeygen/apikeygen.go b/pkg/components/apikeygen/apikeygen.go index 307a59e0e7e..310188a80ef 100644 --- a/pkg/components/apikeygen/apikeygen.go +++ b/pkg/components/apikeygen/apikeygen.go @@ -1,30 +1,58 @@ package apikeygen import ( - "strconv" + "encoding/base64" + "encoding/json" + "errors" "github.com/grafana/grafana/pkg/util" ) +var ErrInvalidApiKey = errors.New("Invalid Api Key") + type KeyGenResult struct { - HashedKey string - JsonKeyEncoded string + HashedKey string + ClientSecret string } type ApiKeyJson struct { - Key string - AccountId int64 - Name string + Key string `json:"k"` + Name string `json:"n"` + OrgId int64 `json:"id"` } -func GenerateNewKey(accountId int64, name string) KeyGenResult { +func New(orgId int64, name string) KeyGenResult { jsonKey := ApiKeyJson{} - jsonKey.AccountId = accountId + jsonKey.OrgId = orgId jsonKey.Name = name jsonKey.Key = util.GetRandomString(32) result := KeyGenResult{} - result.HashedKey = util.EncodePassword([]byte(jsonKey.Key), []byte(strconv.FormatInt(accountId, 10))) + result.HashedKey = util.EncodePassword(jsonKey.Key, name) + jsonString, _ := json.Marshal(jsonKey) + + result.ClientSecret = base64.StdEncoding.EncodeToString([]byte(jsonString)) + return result +} + +func Decode(keyString string) (*ApiKeyJson, error) { + jsonString, err := base64.StdEncoding.DecodeString(keyString) + if err != nil { + return nil, ErrInvalidApiKey + } + + var keyObj ApiKeyJson + err = json.Unmarshal([]byte(jsonString), &keyObj) + if err != nil { + return nil, ErrInvalidApiKey + } + + return &keyObj, nil +} + +func IsValid(key *ApiKeyJson, hashedKey string) bool { + check := util.EncodePassword(key.Key, key.Name) + return check == hashedKey } diff --git a/pkg/components/apikeygen/apikeygen_test.go b/pkg/components/apikeygen/apikeygen_test.go new file mode 100644 index 00000000000..13390d42a18 --- /dev/null +++ b/pkg/components/apikeygen/apikeygen_test.go @@ -0,0 +1,26 @@ +package apikeygen + +import ( + "testing" + + "github.com/grafana/grafana/pkg/util" + . "github.com/smartystreets/goconvey/convey" +) + +func TestApiKeyGen(t *testing.T) { + + Convey("When generating new api key", t, func() { + result := New(12, "Cool key") + + So(result.ClientSecret, ShouldNotBeEmpty) + So(result.HashedKey, ShouldNotBeEmpty) + + Convey("can decode key", func() { + keyInfo, err := Decode(result.ClientSecret) + So(err, ShouldBeNil) + + keyHashed := util.EncodePassword(keyInfo.Key, keyInfo.Name) + So(keyHashed, ShouldEqual, result.HashedKey) + }) + }) +} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 61b0bb570ab..ecc8a0b3ad7 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -9,6 +9,7 @@ import ( "github.com/macaron-contrib/session" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" @@ -43,22 +44,34 @@ func GetContextHandler() macaron.Handler { ctx.SignedInUser = query.Result ctx.IsSignedIn = true } - } else if key := getApiKey(ctx); key != "" { - // Try API Key auth - keyQuery := m.GetApiKeyByKeyQuery{Key: key} + } else if keyString := getApiKey(ctx); keyString != "" { + // base64 decode key + decoded, err := apikeygen.Decode(keyString) + if err != nil { + ctx.JsonApiErr(401, "Invalid API key", err) + return + } + // fetch key + keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId} if err := bus.Dispatch(&keyQuery); err != nil { ctx.JsonApiErr(401, "Invalid API key", err) return } else { - keyInfo := keyQuery.Result + apikey := keyQuery.Result + + // validate api key + if !apikeygen.IsValid(decoded, apikey.Key) { + ctx.JsonApiErr(401, "Invalid API key", err) + return + } ctx.IsSignedIn = true ctx.SignedInUser = &m.SignedInUser{} // TODO: fix this - ctx.OrgRole = keyInfo.Role - ctx.ApiKeyId = keyInfo.Id - ctx.OrgId = keyInfo.OrgId + ctx.OrgRole = apikey.Role + ctx.ApiKeyId = apikey.Id + ctx.OrgId = apikey.OrgId } } else if setting.AnonymousEnabled { orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName} diff --git a/pkg/models/apikey.go b/pkg/models/apikey.go index 148e64b4946..f550e4433db 100644 --- a/pkg/models/apikey.go +++ b/pkg/models/apikey.go @@ -49,9 +49,10 @@ type GetApiKeysQuery struct { Result []*ApiKey } -type GetApiKeyByKeyQuery struct { - Key string - Result *ApiKey +type GetApiKeyByNameQuery struct { + KeyName string + OrgId int64 + Result *ApiKey } // ------------------------ diff --git a/pkg/services/sqlstore/apikey.go b/pkg/services/sqlstore/apikey.go index d696dbbfa27..f4fc88fa211 100644 --- a/pkg/services/sqlstore/apikey.go +++ b/pkg/services/sqlstore/apikey.go @@ -10,8 +10,7 @@ import ( func init() { bus.AddHandler("sql", GetApiKeys) - bus.AddHandler("sql", GetApiKeyByKey) - bus.AddHandler("sql", UpdateApiKey) + bus.AddHandler("sql", GetApiKeyByName) bus.AddHandler("sql", DeleteApiKey) bus.AddHandler("sql", AddApiKey) } @@ -50,23 +49,9 @@ func AddApiKey(cmd *m.AddApiKeyCommand) error { }) } -func UpdateApiKey(cmd *m.UpdateApiKeyCommand) error { - return inTransaction(func(sess *xorm.Session) error { - t := m.ApiKey{ - Id: cmd.Id, - OrgId: cmd.OrgId, - Name: cmd.Name, - Role: cmd.Role, - Updated: time.Now(), - } - _, err := sess.Where("id=? and org_id=?", t.Id, t.OrgId).Update(&t) - return err - }) -} - -func GetApiKeyByKey(query *m.GetApiKeyByKeyQuery) error { +func GetApiKeyByName(query *m.GetApiKeyByNameQuery) error { var apikey m.ApiKey - has, err := x.Where("`key`=?", query.Key).Get(&apikey) + has, err := x.Where("org_id=? AND name=?", query.OrgId, query.KeyName).Get(&apikey) if err != nil { return err diff --git a/pkg/services/sqlstore/apikey_test.go b/pkg/services/sqlstore/apikey_test.go index 6f54d6b8627..e86a1c0db33 100644 --- a/pkg/services/sqlstore/apikey_test.go +++ b/pkg/services/sqlstore/apikey_test.go @@ -14,13 +14,13 @@ func TestApiKeyDataAccess(t *testing.T) { InitTestDB(t) Convey("Given saved api key", func() { - cmd := m.AddApiKeyCommand{OrgId: 1, Key: "hello"} + cmd := m.AddApiKeyCommand{OrgId: 1, Name: "hello", Key: "asd"} err := AddApiKey(&cmd) So(err, ShouldBeNil) - Convey("Should be able to get key by key", func() { - query := m.GetApiKeyByKeyQuery{Key: "hello"} - err = GetApiKeyByKey(&query) + Convey("Should be able to get key by name", func() { + query := m.GetApiKeyByNameQuery{KeyName: "hello", OrgId: 1} + err = GetApiKeyByName(&query) So(err, ShouldBeNil) So(query.Result, ShouldNotBeNil) diff --git a/src/app/features/org/orgApiKeysCtrl.js b/src/app/features/org/orgApiKeysCtrl.js index fa77441d7b5..a8b05155401 100644 --- a/src/app/features/org/orgApiKeysCtrl.js +++ b/src/app/features/org/orgApiKeysCtrl.js @@ -26,7 +26,16 @@ function (angular) { }; $scope.addToken = function() { - backendSrv.post('/api/auth/keys', $scope.token).then($scope.getTokens); + backendSrv.post('/api/auth/keys', $scope.token).then(function(result) { + + var modalScope = $scope.$new(true); + modalScope.key = result.key; + + $scope.appEvent('show-modal', { + src: './app/features/org/partials/apikeyModal.html', + scope: modalScope + }); + }); }; $scope.init(); diff --git a/src/app/features/org/partials/apikeyModal.html b/src/app/features/org/partials/apikeyModal.html new file mode 100644 index 00000000000..93503ed32f6 --- /dev/null +++ b/src/app/features/org/partials/apikeyModal.html @@ -0,0 +1,44 @@ + + diff --git a/src/app/features/org/partials/orgApiKeys.html b/src/app/features/org/partials/orgApiKeys.html index 96763856dec..9db700ab437 100644 --- a/src/app/features/org/partials/orgApiKeys.html +++ b/src/app/features/org/partials/orgApiKeys.html @@ -31,13 +31,16 @@
-
- +
+ + + + + -
NameRole
{{t.name}} {{t.role}}{{t.key}} diff --git a/src/app/features/org/partials/orgDetails.html b/src/app/features/org/partials/orgDetails.html index e3a21706913..0537154aa9f 100644 --- a/src/app/features/org/partials/orgDetails.html +++ b/src/app/features/org/partials/orgDetails.html @@ -28,7 +28,7 @@ Address 1
  • - +
  • @@ -39,7 +39,7 @@ Address 2
  • - +
  • @@ -50,7 +50,7 @@ City
  • - +