]> source.dussan.org Git - gitea.git/commitdiff
Keys API changes (#4960)
authorzeripath <art27@cantab.net>
Thu, 1 Nov 2018 03:40:49 +0000 (03:40 +0000)
committertechknowlogick <hello@techknowlogick.com>
Thu, 1 Nov 2018 03:40:49 +0000 (23:40 -0400)
* Add private information to the deploy keys api

This commit adds more information to the deploy keys to allow for back
reference in to the main keys list. It also adds information about the
repository that the key is referring to.

Signed-off-by: Andrew Thornton <art27@cantab.net>
* Add private information to the user keys API

This adjusts the keys API to give out private information to user keys if
the current user is the owner or an admin.

Signed-off-by: Andrew Thornton <art27@cantab.net>
* Add ability to search keys by fingerprint

This commit adds the functionality to search ssh-keys by fingerprint of
the ssh-key. Deploy keys per repository can also be searched. There is
no current clear API point to allow search of all deploy keys by
fingerprint or keyID.

Signed-off-by: Andrew Thornton <art27@cantab.net>
* Add integration test

integrations/api_keys_test.go
models/ssh_key.go
routers/api/v1/convert/convert.go
routers/api/v1/repo/key.go
routers/api/v1/user/key.go
templates/swagger/v1_json.tmpl

index 8c83ae42c500381bef43a4e017dde496c4163731..91cbd72f917fe5c710c6fcba8736fb5a67da078c 100644 (file)
@@ -7,8 +7,11 @@ package integrations
 import (
        "fmt"
        "net/http"
+       "net/url"
        "testing"
 
+       "github.com/stretchr/testify/assert"
+
        "code.gitea.io/gitea/models"
        api "code.gitea.io/sdk/gitea"
 )
@@ -90,3 +93,102 @@ func TestCreateReadWriteDeployKey(t *testing.T) {
                Mode:    models.AccessModeWrite,
        })
 }
+
+func TestCreateUserKey(t *testing.T) {
+       prepareTestEnv(t)
+       user := models.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User)
+
+       session := loginUser(t, "user1")
+       token := url.QueryEscape(getTokenForLoggedInUser(t, session))
+       keysURL := fmt.Sprintf("/api/v1/user/keys?token=%s", token)
+       keyType := "ssh-rsa"
+       keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCyTiPTeHJl6Gs5D1FyHT0qTWpVkAy9+LIKjctQXklrePTvUNVrSpt4r2exFYXNMPeA8V0zCrc3Kzs1SZw3jWkG3i53te9onCp85DqyatxOD2pyZ30/gPn1ZUg40WowlFM8gsUFMZqaH7ax6d8nsBKW7N/cRyqesiOQEV9up3tnKjIB8XMTVvC5X4rBWgywz7AFxSv8mmaTHnUgVW4LgMPwnTWo0pxtiIWbeMLyrEE4hIM74gSwp6CRQYo6xnG3fn4yWkcK2X2mT9adQ241IDdwpENJHcry/T6AJ8dNXduEZ67egnk+rVlQ2HM4LpymAv9DAAFFeaQK0hT+3aMDoumV"
+       rawKeyBody := api.CreateKeyOption{
+               Title: "test-key",
+               Key:   keyType + " " + keyContent,
+       }
+       req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody)
+       resp := session.MakeRequest(t, req, http.StatusCreated)
+
+       var newPublicKey api.PublicKey
+       DecodeJSON(t, resp, &newPublicKey)
+       models.AssertExistsAndLoadBean(t, &models.PublicKey{
+               ID:      newPublicKey.ID,
+               OwnerID: user.ID,
+               Name:    rawKeyBody.Title,
+               Content: rawKeyBody.Key,
+               Mode:    models.AccessModeWrite,
+       })
+
+       // Search by fingerprint
+       fingerprintURL := fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%s", token, newPublicKey.Fingerprint)
+
+       req = NewRequest(t, "GET", fingerprintURL)
+       resp = session.MakeRequest(t, req, http.StatusOK)
+
+       var fingerprintPublicKeys []api.PublicKey
+       DecodeJSON(t, resp, &fingerprintPublicKeys)
+       assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
+       assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
+       assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID)
+
+       fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", user.Name, token, newPublicKey.Fingerprint)
+
+       req = NewRequest(t, "GET", fingerprintURL)
+       resp = session.MakeRequest(t, req, http.StatusOK)
+
+       DecodeJSON(t, resp, &fingerprintPublicKeys)
+       assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
+       assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
+       assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID)
+
+       // Fail search by fingerprint
+       fingerprintURL = fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%sA", token, newPublicKey.Fingerprint)
+
+       req = NewRequest(t, "GET", fingerprintURL)
+       resp = session.MakeRequest(t, req, http.StatusOK)
+
+       DecodeJSON(t, resp, &fingerprintPublicKeys)
+       assert.Len(t, fingerprintPublicKeys, 0)
+
+       // Fail searching for wrong users key
+       fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", "user2", token, newPublicKey.Fingerprint)
+       req = NewRequest(t, "GET", fingerprintURL)
+       resp = session.MakeRequest(t, req, http.StatusOK)
+
+       DecodeJSON(t, resp, &fingerprintPublicKeys)
+       assert.Len(t, fingerprintPublicKeys, 0)
+
+       // Now login as user 2
+       session2 := loginUser(t, "user2")
+       token2 := url.QueryEscape(getTokenForLoggedInUser(t, session2))
+
+       // Should find key even though not ours, but we shouldn't know whose it is
+       fingerprintURL = fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%s", token2, newPublicKey.Fingerprint)
+       req = NewRequest(t, "GET", fingerprintURL)
+       resp = session.MakeRequest(t, req, http.StatusOK)
+
+       DecodeJSON(t, resp, &fingerprintPublicKeys)
+       assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
+       assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
+       assert.Nil(t, fingerprintPublicKeys[0].Owner)
+
+       // Should find key even though not ours, but we shouldn't know whose it is
+       fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", user.Name, token2, newPublicKey.Fingerprint)
+
+       req = NewRequest(t, "GET", fingerprintURL)
+       resp = session.MakeRequest(t, req, http.StatusOK)
+
+       DecodeJSON(t, resp, &fingerprintPublicKeys)
+       assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
+       assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
+       assert.Nil(t, fingerprintPublicKeys[0].Owner)
+
+       // Fail when searching for key if it is not ours
+       fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", "user2", token2, newPublicKey.Fingerprint)
+       req = NewRequest(t, "GET", fingerprintURL)
+       resp = session.MakeRequest(t, req, http.StatusOK)
+
+       DecodeJSON(t, resp, &fingerprintPublicKeys)
+       assert.Len(t, fingerprintPublicKeys, 0)
+}
index 2592209b4d7a351b8b2cddb0174cb83ab87dc050..0368ffad33e6d05e49cff20e5d0836d3d7ad547e 100644 (file)
@@ -24,6 +24,7 @@ import (
        "code.gitea.io/gitea/modules/util"
 
        "github.com/Unknwon/com"
+       "github.com/go-xorm/builder"
        "github.com/go-xorm/xorm"
        "golang.org/x/crypto/ssh"
 )
@@ -465,6 +466,19 @@ func SearchPublicKeyByContent(content string) (*PublicKey, error) {
        return key, nil
 }
 
+// SearchPublicKey returns a list of public keys matching the provided arguments.
+func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) {
+       keys := make([]*PublicKey, 0, 5)
+       cond := builder.NewCond()
+       if uid != 0 {
+               cond = cond.And(builder.Eq{"owner_id": uid})
+       }
+       if fingerprint != "" {
+               cond = cond.And(builder.Eq{"fingerprint": fingerprint})
+       }
+       return keys, x.Where(cond).Find(&keys)
+}
+
 // ListPublicKeys returns a list of public keys belongs to given user.
 func ListPublicKeys(uid int64) ([]*PublicKey, error) {
        keys := make([]*PublicKey, 0, 5)
@@ -833,3 +847,19 @@ func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
                Where("repo_id = ?", repoID).
                Find(&keys)
 }
+
+// SearchDeployKeys returns a list of deploy keys matching the provided arguments.
+func SearchDeployKeys(repoID int64, keyID int64, fingerprint string) ([]*DeployKey, error) {
+       keys := make([]*DeployKey, 0, 5)
+       cond := builder.NewCond()
+       if repoID != 0 {
+               cond = cond.And(builder.Eq{"repo_id": repoID})
+       }
+       if keyID != 0 {
+               cond = cond.And(builder.Eq{"key_id": keyID})
+       }
+       if fingerprint != "" {
+               cond = cond.And(builder.Eq{"fingerprint": fingerprint})
+       }
+       return keys, x.Where(cond).Find(&keys)
+}
index 19b966971a40aeb354ecae742d7c17c6f92958dd..1bfeae34bfe39c268001c9b6562a9baaba2456b6 100644 (file)
@@ -167,12 +167,14 @@ func ToHook(repoLink string, w *models.Webhook) *api.Hook {
 // ToDeployKey convert models.DeployKey to api.DeployKey
 func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
        return &api.DeployKey{
-               ID:       key.ID,
-               Key:      key.Content,
-               URL:      apiLink + com.ToStr(key.ID),
-               Title:    key.Name,
-               Created:  key.CreatedUnix.AsTime(),
-               ReadOnly: true, // All deploy keys are read-only.
+               ID:          key.ID,
+               KeyID:       key.KeyID,
+               Key:         key.Content,
+               Fingerprint: key.Fingerprint,
+               URL:         apiLink + com.ToStr(key.ID),
+               Title:       key.Name,
+               Created:     key.CreatedUnix.AsTime(),
+               ReadOnly:    key.Mode == models.AccessModeRead, // All deploy keys are read-only.
        }
 }
 
index 89a550cfd318fa38e50dd3f140af4965b933655d..2caca887aadd3d9dc9944927a4849b62c6ab3054 100644 (file)
@@ -15,6 +15,21 @@ import (
        api "code.gitea.io/sdk/gitea"
 )
 
+// appendPrivateInformation appends the owner and key type information to api.PublicKey
+func appendPrivateInformation(apiKey *api.DeployKey, key *models.DeployKey, repository *models.Repository) (*api.DeployKey, error) {
+       apiKey.ReadOnly = key.Mode == models.AccessModeRead
+       if repository.ID == key.RepoID {
+               apiKey.Repository = repository.APIFormat(key.Mode)
+       } else {
+               repo, err := models.GetRepositoryByID(key.RepoID)
+               if err != nil {
+                       return apiKey, err
+               }
+               apiKey.Repository = repo.APIFormat(key.Mode)
+       }
+       return apiKey, nil
+}
+
 func composeDeployKeysAPILink(repoPath string) string {
        return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/"
 }
@@ -37,10 +52,28 @@ func ListDeployKeys(ctx *context.APIContext) {
        //   description: name of the repo
        //   type: string
        //   required: true
+       // - name: key_id
+       //   in: query
+       //   description: the key_id to search for
+       //   type: integer
+       // - name: fingerprint
+       //   in: query
+       //   description: fingerprint of the key
+       //   type: string
        // responses:
        //   "200":
        //     "$ref": "#/responses/DeployKeyList"
-       keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID)
+       var keys []*models.DeployKey
+       var err error
+
+       fingerprint := ctx.Query("fingerprint")
+       keyID := ctx.QueryInt64("key_id")
+       if fingerprint != "" || keyID != 0 {
+               keys, err = models.SearchDeployKeys(ctx.Repo.Repository.ID, keyID, fingerprint)
+       } else {
+               keys, err = models.ListDeployKeys(ctx.Repo.Repository.ID)
+       }
+
        if err != nil {
                ctx.Error(500, "ListDeployKeys", err)
                return
@@ -54,6 +87,9 @@ func ListDeployKeys(ctx *context.APIContext) {
                        return
                }
                apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
+               if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == keys[i].RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) {
+                       apiKeys[i], _ = appendPrivateInformation(apiKeys[i], keys[i], ctx.Repo.Repository)
+               }
        }
 
        ctx.JSON(200, &apiKeys)
@@ -102,7 +138,11 @@ func GetDeployKey(ctx *context.APIContext) {
        }
 
        apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name)
-       ctx.JSON(200, convert.ToDeployKey(apiLink, key))
+       apiKey := convert.ToDeployKey(apiLink, key)
+       if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) {
+               apiKey, _ = appendPrivateInformation(apiKey, key, ctx.Repo.Repository)
+       }
+       ctx.JSON(200, apiKey)
 }
 
 // HandleCheckKeyStringError handle check key error
index e5d1b08f0dab58fc256063d422792ae5df67530a..d8ab752b2be46760c2e0f9d91c5565cf434e6991 100644 (file)
@@ -14,6 +14,29 @@ import (
        "code.gitea.io/gitea/routers/api/v1/repo"
 )
 
+// appendPrivateInformation appends the owner and key type information to api.PublicKey
+func appendPrivateInformation(apiKey *api.PublicKey, key *models.PublicKey, defaultUser *models.User) (*api.PublicKey, error) {
+       if key.Type == models.KeyTypeDeploy {
+               apiKey.KeyType = "deploy"
+       } else if key.Type == models.KeyTypeUser {
+               apiKey.KeyType = "user"
+
+               if defaultUser.ID == key.OwnerID {
+                       apiKey.Owner = defaultUser.APIFormat()
+               } else {
+                       user, err := models.GetUserByID(key.OwnerID)
+                       if err != nil {
+                               return apiKey, err
+                       }
+                       apiKey.Owner = user.APIFormat()
+               }
+       } else {
+               apiKey.KeyType = "unknown"
+       }
+       apiKey.ReadOnly = key.Mode == models.AccessModeRead
+       return apiKey, nil
+}
+
 // GetUserByParamsName get user by name
 func GetUserByParamsName(ctx *context.APIContext, name string) *models.User {
        user, err := models.GetUserByName(ctx.Params(name))
@@ -37,8 +60,27 @@ func composePublicKeysAPILink() string {
        return setting.AppURL + "api/v1/user/keys/"
 }
 
-func listPublicKeys(ctx *context.APIContext, uid int64) {
-       keys, err := models.ListPublicKeys(uid)
+func listPublicKeys(ctx *context.APIContext, user *models.User) {
+       var keys []*models.PublicKey
+       var err error
+
+       fingerprint := ctx.Query("fingerprint")
+       username := ctx.Params("username")
+
+       if fingerprint != "" {
+               // Querying not just listing
+               if username != "" {
+                       // Restrict to provided uid
+                       keys, err = models.SearchPublicKey(user.ID, fingerprint)
+               } else {
+                       // Unrestricted
+                       keys, err = models.SearchPublicKey(0, fingerprint)
+               }
+       } else {
+               // Use ListPublicKeys
+               keys, err = models.ListPublicKeys(user.ID)
+       }
+
        if err != nil {
                ctx.Error(500, "ListPublicKeys", err)
                return
@@ -48,6 +90,9 @@ func listPublicKeys(ctx *context.APIContext, uid int64) {
        apiKeys := make([]*api.PublicKey, len(keys))
        for i := range keys {
                apiKeys[i] = convert.ToPublicKey(apiLink, keys[i])
+               if ctx.User.IsAdmin || ctx.User.ID == keys[i].OwnerID {
+                       apiKeys[i], _ = appendPrivateInformation(apiKeys[i], keys[i], user)
+               }
        }
 
        ctx.JSON(200, &apiKeys)
@@ -58,12 +103,17 @@ func ListMyPublicKeys(ctx *context.APIContext) {
        // swagger:operation GET /user/keys user userCurrentListKeys
        // ---
        // summary: List the authenticated user's public keys
+       // parameters:
+       // - name: fingerprint
+       //   in: query
+       //   description: fingerprint of the key
+       //   type: string
        // produces:
        // - application/json
        // responses:
        //   "200":
        //     "$ref": "#/responses/PublicKeyList"
-       listPublicKeys(ctx, ctx.User.ID)
+       listPublicKeys(ctx, ctx.User)
 }
 
 // ListPublicKeys list the given user's public keys
@@ -79,6 +129,10 @@ func ListPublicKeys(ctx *context.APIContext) {
        //   description: username of user
        //   type: string
        //   required: true
+       // - name: fingerprint
+       //   in: query
+       //   description: fingerprint of the key
+       //   type: string
        // responses:
        //   "200":
        //     "$ref": "#/responses/PublicKeyList"
@@ -86,7 +140,7 @@ func ListPublicKeys(ctx *context.APIContext) {
        if ctx.Written() {
                return
        }
-       listPublicKeys(ctx, user.ID)
+       listPublicKeys(ctx, user)
 }
 
 // GetPublicKey get a public key
@@ -119,7 +173,11 @@ func GetPublicKey(ctx *context.APIContext) {
        }
 
        apiLink := composePublicKeysAPILink()
-       ctx.JSON(200, convert.ToPublicKey(apiLink, key))
+       apiKey := convert.ToPublicKey(apiLink, key)
+       if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID {
+               apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User)
+       }
+       ctx.JSON(200, apiKey)
 }
 
 // CreateUserPublicKey creates new public key to given user by ID.
@@ -136,7 +194,11 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid
                return
        }
        apiLink := composePublicKeysAPILink()
-       ctx.JSON(201, convert.ToPublicKey(apiLink, key))
+       apiKey := convert.ToPublicKey(apiLink, key)
+       if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID {
+               apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User)
+       }
+       ctx.JSON(201, apiKey)
 }
 
 // CreatePublicKey create one public key for me
index efba90b18b5a87343e39ce9a934eb2c764aef42b..56a169c295d52a28e76e59325beae15680d9bc28 100644 (file)
             "name": "repo",
             "in": "path",
             "required": true
+          },
+          {
+            "type": "integer",
+            "description": "the key_id to search for",
+            "name": "key_id",
+            "in": "query"
+          },
+          {
+            "type": "string",
+            "description": "fingerprint of the key",
+            "name": "fingerprint",
+            "in": "query"
           }
         ],
         "responses": {
         ],
         "summary": "List the authenticated user's public keys",
         "operationId": "userCurrentListKeys",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "fingerprint of the key",
+            "name": "fingerprint",
+            "in": "query"
+          }
+        ],
         "responses": {
           "200": {
             "$ref": "#/responses/PublicKeyList"
             "name": "username",
             "in": "path",
             "required": true
+          },
+          {
+            "type": "string",
+            "description": "fingerprint of the key",
+            "name": "fingerprint",
+            "in": "query"
           }
         ],
         "responses": {