Skip to content

Commit 00533d3

Browse files
zeripathtechknowlogick
authored andcommitted
Keys API changes (#4960)
* 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * Add integration test
1 parent 584844e commit 00533d3

File tree

6 files changed

+276
-14
lines changed

6 files changed

+276
-14
lines changed

integrations/api_keys_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ package integrations
77
import (
88
"fmt"
99
"net/http"
10+
"net/url"
1011
"testing"
1112

13+
"github.com/stretchr/testify/assert"
14+
1215
"code.gitea.io/gitea/models"
1316
api "code.gitea.io/sdk/gitea"
1417
)
@@ -90,3 +93,102 @@ func TestCreateReadWriteDeployKey(t *testing.T) {
9093
Mode: models.AccessModeWrite,
9194
})
9295
}
96+
97+
func TestCreateUserKey(t *testing.T) {
98+
prepareTestEnv(t)
99+
user := models.AssertExistsAndLoadBean(t, &models.User{Name: "user1"}).(*models.User)
100+
101+
session := loginUser(t, "user1")
102+
token := url.QueryEscape(getTokenForLoggedInUser(t, session))
103+
keysURL := fmt.Sprintf("/api/v1/user/keys?token=%s", token)
104+
keyType := "ssh-rsa"
105+
keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCyTiPTeHJl6Gs5D1FyHT0qTWpVkAy9+LIKjctQXklrePTvUNVrSpt4r2exFYXNMPeA8V0zCrc3Kzs1SZw3jWkG3i53te9onCp85DqyatxOD2pyZ30/gPn1ZUg40WowlFM8gsUFMZqaH7ax6d8nsBKW7N/cRyqesiOQEV9up3tnKjIB8XMTVvC5X4rBWgywz7AFxSv8mmaTHnUgVW4LgMPwnTWo0pxtiIWbeMLyrEE4hIM74gSwp6CRQYo6xnG3fn4yWkcK2X2mT9adQ241IDdwpENJHcry/T6AJ8dNXduEZ67egnk+rVlQ2HM4LpymAv9DAAFFeaQK0hT+3aMDoumV"
106+
rawKeyBody := api.CreateKeyOption{
107+
Title: "test-key",
108+
Key: keyType + " " + keyContent,
109+
}
110+
req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody)
111+
resp := session.MakeRequest(t, req, http.StatusCreated)
112+
113+
var newPublicKey api.PublicKey
114+
DecodeJSON(t, resp, &newPublicKey)
115+
models.AssertExistsAndLoadBean(t, &models.PublicKey{
116+
ID: newPublicKey.ID,
117+
OwnerID: user.ID,
118+
Name: rawKeyBody.Title,
119+
Content: rawKeyBody.Key,
120+
Mode: models.AccessModeWrite,
121+
})
122+
123+
// Search by fingerprint
124+
fingerprintURL := fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%s", token, newPublicKey.Fingerprint)
125+
126+
req = NewRequest(t, "GET", fingerprintURL)
127+
resp = session.MakeRequest(t, req, http.StatusOK)
128+
129+
var fingerprintPublicKeys []api.PublicKey
130+
DecodeJSON(t, resp, &fingerprintPublicKeys)
131+
assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
132+
assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
133+
assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID)
134+
135+
fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", user.Name, token, newPublicKey.Fingerprint)
136+
137+
req = NewRequest(t, "GET", fingerprintURL)
138+
resp = session.MakeRequest(t, req, http.StatusOK)
139+
140+
DecodeJSON(t, resp, &fingerprintPublicKeys)
141+
assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
142+
assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
143+
assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID)
144+
145+
// Fail search by fingerprint
146+
fingerprintURL = fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%sA", token, newPublicKey.Fingerprint)
147+
148+
req = NewRequest(t, "GET", fingerprintURL)
149+
resp = session.MakeRequest(t, req, http.StatusOK)
150+
151+
DecodeJSON(t, resp, &fingerprintPublicKeys)
152+
assert.Len(t, fingerprintPublicKeys, 0)
153+
154+
// Fail searching for wrong users key
155+
fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", "user2", token, newPublicKey.Fingerprint)
156+
req = NewRequest(t, "GET", fingerprintURL)
157+
resp = session.MakeRequest(t, req, http.StatusOK)
158+
159+
DecodeJSON(t, resp, &fingerprintPublicKeys)
160+
assert.Len(t, fingerprintPublicKeys, 0)
161+
162+
// Now login as user 2
163+
session2 := loginUser(t, "user2")
164+
token2 := url.QueryEscape(getTokenForLoggedInUser(t, session2))
165+
166+
// Should find key even though not ours, but we shouldn't know whose it is
167+
fingerprintURL = fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%s", token2, newPublicKey.Fingerprint)
168+
req = NewRequest(t, "GET", fingerprintURL)
169+
resp = session.MakeRequest(t, req, http.StatusOK)
170+
171+
DecodeJSON(t, resp, &fingerprintPublicKeys)
172+
assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
173+
assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
174+
assert.Nil(t, fingerprintPublicKeys[0].Owner)
175+
176+
// Should find key even though not ours, but we shouldn't know whose it is
177+
fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", user.Name, token2, newPublicKey.Fingerprint)
178+
179+
req = NewRequest(t, "GET", fingerprintURL)
180+
resp = session.MakeRequest(t, req, http.StatusOK)
181+
182+
DecodeJSON(t, resp, &fingerprintPublicKeys)
183+
assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint)
184+
assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID)
185+
assert.Nil(t, fingerprintPublicKeys[0].Owner)
186+
187+
// Fail when searching for key if it is not ours
188+
fingerprintURL = fmt.Sprintf("/api/v1/users/%s/keys?token=%s&fingerprint=%s", "user2", token2, newPublicKey.Fingerprint)
189+
req = NewRequest(t, "GET", fingerprintURL)
190+
resp = session.MakeRequest(t, req, http.StatusOK)
191+
192+
DecodeJSON(t, resp, &fingerprintPublicKeys)
193+
assert.Len(t, fingerprintPublicKeys, 0)
194+
}

models/ssh_key.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"code.gitea.io/gitea/modules/util"
2525

2626
"github.com/Unknwon/com"
27+
"github.com/go-xorm/builder"
2728
"github.com/go-xorm/xorm"
2829
"golang.org/x/crypto/ssh"
2930
)
@@ -465,6 +466,19 @@ func SearchPublicKeyByContent(content string) (*PublicKey, error) {
465466
return key, nil
466467
}
467468

469+
// SearchPublicKey returns a list of public keys matching the provided arguments.
470+
func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) {
471+
keys := make([]*PublicKey, 0, 5)
472+
cond := builder.NewCond()
473+
if uid != 0 {
474+
cond = cond.And(builder.Eq{"owner_id": uid})
475+
}
476+
if fingerprint != "" {
477+
cond = cond.And(builder.Eq{"fingerprint": fingerprint})
478+
}
479+
return keys, x.Where(cond).Find(&keys)
480+
}
481+
468482
// ListPublicKeys returns a list of public keys belongs to given user.
469483
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
470484
keys := make([]*PublicKey, 0, 5)
@@ -833,3 +847,19 @@ func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
833847
Where("repo_id = ?", repoID).
834848
Find(&keys)
835849
}
850+
851+
// SearchDeployKeys returns a list of deploy keys matching the provided arguments.
852+
func SearchDeployKeys(repoID int64, keyID int64, fingerprint string) ([]*DeployKey, error) {
853+
keys := make([]*DeployKey, 0, 5)
854+
cond := builder.NewCond()
855+
if repoID != 0 {
856+
cond = cond.And(builder.Eq{"repo_id": repoID})
857+
}
858+
if keyID != 0 {
859+
cond = cond.And(builder.Eq{"key_id": keyID})
860+
}
861+
if fingerprint != "" {
862+
cond = cond.And(builder.Eq{"fingerprint": fingerprint})
863+
}
864+
return keys, x.Where(cond).Find(&keys)
865+
}

routers/api/v1/convert/convert.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ func ToHook(repoLink string, w *models.Webhook) *api.Hook {
167167
// ToDeployKey convert models.DeployKey to api.DeployKey
168168
func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
169169
return &api.DeployKey{
170-
ID: key.ID,
171-
Key: key.Content,
172-
URL: apiLink + com.ToStr(key.ID),
173-
Title: key.Name,
174-
Created: key.CreatedUnix.AsTime(),
175-
ReadOnly: true, // All deploy keys are read-only.
170+
ID: key.ID,
171+
KeyID: key.KeyID,
172+
Key: key.Content,
173+
Fingerprint: key.Fingerprint,
174+
URL: apiLink + com.ToStr(key.ID),
175+
Title: key.Name,
176+
Created: key.CreatedUnix.AsTime(),
177+
ReadOnly: key.Mode == models.AccessModeRead, // All deploy keys are read-only.
176178
}
177179
}
178180

routers/api/v1/repo/key.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ import (
1515
api "code.gitea.io/sdk/gitea"
1616
)
1717

18+
// appendPrivateInformation appends the owner and key type information to api.PublicKey
19+
func appendPrivateInformation(apiKey *api.DeployKey, key *models.DeployKey, repository *models.Repository) (*api.DeployKey, error) {
20+
apiKey.ReadOnly = key.Mode == models.AccessModeRead
21+
if repository.ID == key.RepoID {
22+
apiKey.Repository = repository.APIFormat(key.Mode)
23+
} else {
24+
repo, err := models.GetRepositoryByID(key.RepoID)
25+
if err != nil {
26+
return apiKey, err
27+
}
28+
apiKey.Repository = repo.APIFormat(key.Mode)
29+
}
30+
return apiKey, nil
31+
}
32+
1833
func composeDeployKeysAPILink(repoPath string) string {
1934
return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/"
2035
}
@@ -37,10 +52,28 @@ func ListDeployKeys(ctx *context.APIContext) {
3752
// description: name of the repo
3853
// type: string
3954
// required: true
55+
// - name: key_id
56+
// in: query
57+
// description: the key_id to search for
58+
// type: integer
59+
// - name: fingerprint
60+
// in: query
61+
// description: fingerprint of the key
62+
// type: string
4063
// responses:
4164
// "200":
4265
// "$ref": "#/responses/DeployKeyList"
43-
keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID)
66+
var keys []*models.DeployKey
67+
var err error
68+
69+
fingerprint := ctx.Query("fingerprint")
70+
keyID := ctx.QueryInt64("key_id")
71+
if fingerprint != "" || keyID != 0 {
72+
keys, err = models.SearchDeployKeys(ctx.Repo.Repository.ID, keyID, fingerprint)
73+
} else {
74+
keys, err = models.ListDeployKeys(ctx.Repo.Repository.ID)
75+
}
76+
4477
if err != nil {
4578
ctx.Error(500, "ListDeployKeys", err)
4679
return
@@ -54,6 +87,9 @@ func ListDeployKeys(ctx *context.APIContext) {
5487
return
5588
}
5689
apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
90+
if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == keys[i].RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) {
91+
apiKeys[i], _ = appendPrivateInformation(apiKeys[i], keys[i], ctx.Repo.Repository)
92+
}
5793
}
5894

5995
ctx.JSON(200, &apiKeys)
@@ -102,7 +138,11 @@ func GetDeployKey(ctx *context.APIContext) {
102138
}
103139

104140
apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name)
105-
ctx.JSON(200, convert.ToDeployKey(apiLink, key))
141+
apiKey := convert.ToDeployKey(apiLink, key)
142+
if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) {
143+
apiKey, _ = appendPrivateInformation(apiKey, key, ctx.Repo.Repository)
144+
}
145+
ctx.JSON(200, apiKey)
106146
}
107147

108148
// HandleCheckKeyStringError handle check key error

routers/api/v1/user/key.go

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,29 @@ import (
1414
"code.gitea.io/gitea/routers/api/v1/repo"
1515
)
1616

17+
// appendPrivateInformation appends the owner and key type information to api.PublicKey
18+
func appendPrivateInformation(apiKey *api.PublicKey, key *models.PublicKey, defaultUser *models.User) (*api.PublicKey, error) {
19+
if key.Type == models.KeyTypeDeploy {
20+
apiKey.KeyType = "deploy"
21+
} else if key.Type == models.KeyTypeUser {
22+
apiKey.KeyType = "user"
23+
24+
if defaultUser.ID == key.OwnerID {
25+
apiKey.Owner = defaultUser.APIFormat()
26+
} else {
27+
user, err := models.GetUserByID(key.OwnerID)
28+
if err != nil {
29+
return apiKey, err
30+
}
31+
apiKey.Owner = user.APIFormat()
32+
}
33+
} else {
34+
apiKey.KeyType = "unknown"
35+
}
36+
apiKey.ReadOnly = key.Mode == models.AccessModeRead
37+
return apiKey, nil
38+
}
39+
1740
// GetUserByParamsName get user by name
1841
func GetUserByParamsName(ctx *context.APIContext, name string) *models.User {
1942
user, err := models.GetUserByName(ctx.Params(name))
@@ -37,8 +60,27 @@ func composePublicKeysAPILink() string {
3760
return setting.AppURL + "api/v1/user/keys/"
3861
}
3962

40-
func listPublicKeys(ctx *context.APIContext, uid int64) {
41-
keys, err := models.ListPublicKeys(uid)
63+
func listPublicKeys(ctx *context.APIContext, user *models.User) {
64+
var keys []*models.PublicKey
65+
var err error
66+
67+
fingerprint := ctx.Query("fingerprint")
68+
username := ctx.Params("username")
69+
70+
if fingerprint != "" {
71+
// Querying not just listing
72+
if username != "" {
73+
// Restrict to provided uid
74+
keys, err = models.SearchPublicKey(user.ID, fingerprint)
75+
} else {
76+
// Unrestricted
77+
keys, err = models.SearchPublicKey(0, fingerprint)
78+
}
79+
} else {
80+
// Use ListPublicKeys
81+
keys, err = models.ListPublicKeys(user.ID)
82+
}
83+
4284
if err != nil {
4385
ctx.Error(500, "ListPublicKeys", err)
4486
return
@@ -48,6 +90,9 @@ func listPublicKeys(ctx *context.APIContext, uid int64) {
4890
apiKeys := make([]*api.PublicKey, len(keys))
4991
for i := range keys {
5092
apiKeys[i] = convert.ToPublicKey(apiLink, keys[i])
93+
if ctx.User.IsAdmin || ctx.User.ID == keys[i].OwnerID {
94+
apiKeys[i], _ = appendPrivateInformation(apiKeys[i], keys[i], user)
95+
}
5196
}
5297

5398
ctx.JSON(200, &apiKeys)
@@ -58,12 +103,17 @@ func ListMyPublicKeys(ctx *context.APIContext) {
58103
// swagger:operation GET /user/keys user userCurrentListKeys
59104
// ---
60105
// summary: List the authenticated user's public keys
106+
// parameters:
107+
// - name: fingerprint
108+
// in: query
109+
// description: fingerprint of the key
110+
// type: string
61111
// produces:
62112
// - application/json
63113
// responses:
64114
// "200":
65115
// "$ref": "#/responses/PublicKeyList"
66-
listPublicKeys(ctx, ctx.User.ID)
116+
listPublicKeys(ctx, ctx.User)
67117
}
68118

69119
// ListPublicKeys list the given user's public keys
@@ -79,14 +129,18 @@ func ListPublicKeys(ctx *context.APIContext) {
79129
// description: username of user
80130
// type: string
81131
// required: true
132+
// - name: fingerprint
133+
// in: query
134+
// description: fingerprint of the key
135+
// type: string
82136
// responses:
83137
// "200":
84138
// "$ref": "#/responses/PublicKeyList"
85139
user := GetUserByParams(ctx)
86140
if ctx.Written() {
87141
return
88142
}
89-
listPublicKeys(ctx, user.ID)
143+
listPublicKeys(ctx, user)
90144
}
91145

92146
// GetPublicKey get a public key
@@ -119,7 +173,11 @@ func GetPublicKey(ctx *context.APIContext) {
119173
}
120174

121175
apiLink := composePublicKeysAPILink()
122-
ctx.JSON(200, convert.ToPublicKey(apiLink, key))
176+
apiKey := convert.ToPublicKey(apiLink, key)
177+
if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID {
178+
apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User)
179+
}
180+
ctx.JSON(200, apiKey)
123181
}
124182

125183
// CreateUserPublicKey creates new public key to given user by ID.
@@ -136,7 +194,11 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid
136194
return
137195
}
138196
apiLink := composePublicKeysAPILink()
139-
ctx.JSON(201, convert.ToPublicKey(apiLink, key))
197+
apiKey := convert.ToPublicKey(apiLink, key)
198+
if ctx.User.IsAdmin || ctx.User.ID == key.OwnerID {
199+
apiKey, _ = appendPrivateInformation(apiKey, key, ctx.User)
200+
}
201+
ctx.JSON(201, apiKey)
140202
}
141203

142204
// CreatePublicKey create one public key for me

0 commit comments

Comments
 (0)