* add .gpg url (match github behaviour) * wildcard * test to export maximum data * working POC * add comment for old imported keys * cleaning * Update routers/user/profile.go Co-Authored-By: sapk <sapk@users.noreply.github.com> * add migration script * add integration teststags/v1.21.12.1
| @@ -101,3 +101,90 @@ func TestRenameReservedUsername(t *testing.T) { | |||||
| models.AssertNotExistsBean(t, &models.User{Name: reservedUsername}) | models.AssertNotExistsBean(t, &models.User{Name: reservedUsername}) | ||||
| } | } | ||||
| } | } | ||||
| func TestExportUserGPGKeys(t *testing.T) { | |||||
| prepareTestEnv(t) | |||||
| //Export empty key list | |||||
| testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK----- | |||||
| =twTO | |||||
| -----END PGP PUBLIC KEY BLOCK----- | |||||
| `) | |||||
| //Import key | |||||
| //User1 <user1@example.com> | |||||
| session := loginUser(t, "user1") | |||||
| token := getTokenForLoggedInUser(t, session) | |||||
| testCreateGPGKey(t, session.MakeRequest, token, http.StatusCreated, `-----BEGIN PGP PUBLIC KEY BLOCK----- | |||||
| mQENBFyy/VUBCADJ7zbM20Z1RWmFoVgp5WkQfI2rU1Vj9cQHes9i42wVLLtcbPeo | |||||
| QzubgzvMPITDy7nfWxgSf83E23DoHQ1ACFbQh/6eFSRrjsusp3YQ/08NSfPPbcu8 | |||||
| 0M5G+VGwSfzS5uEcwBVQmHyKdcOZIERTNMtYZx1C3bjLD1XVJHvWz9D72Uq4qeO3 | |||||
| 8SR+lzp5n6ppUakcmRnxt3nGRBj1+hEGkdgzyPo93iy+WioegY2lwCA9xMEo5dah | |||||
| BmYxWx51zyiXYlReTaxlyb3/nuSUt8IcW3Q8zjdtJj4Nu8U1SpV8EdaA1I9IPbHW | |||||
| 510OSLmD3XhqHH5m6mIxL1YoWxk3V7gpDROtABEBAAG0GVVzZXIxIDx1c2VyMUBl | |||||
| eGFtcGxlLmNvbT6JAU4EEwEIADgWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9 | |||||
| VQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD9+v0I6RSEH22YCACFqL5+ | |||||
| 6M0m18AMC/pumcpnnmvAS1GrrKTF8nOROA1augZwp1WCNuKw2R6uOJIHANrYECSn | |||||
| u7+j6GBP2gbIW8mSAzS6HWCs7GGiPpVtT4wcu8wljUI6BxjpyZtoEkriyBjt6HfK | |||||
| rkegbkuySoJvjq4IcO5D1LB1JWgsUjMYQJj/ZpBIzVtjG9QtFSOiT1Hct4PoZHdC | |||||
| nsdSgyCkwRZXG+u3kT/wP9F663ba4o16vYlz3dCGo66lF2tyoG3qcyZ1OUzUrnuv | |||||
| 96ytAzT6XIhrE0nVoBprMxFF5zExotJD3bHjcGBFNLf944bhjKee3U6t9+OsfJVC | |||||
| l7N5xxIawCuTQdbfuQENBFyy/VUBCADe61yGEoTwKfsOKIhxLaNoRmD883O0tiWt | |||||
| soO/HPj9dPQLTOiwXgSgSCd8C+LNxGKct87wgFozpah4tDLC6c0nALuHJ0SLbkfz | |||||
| 55aRhLeOOcrAydatDp72GroXzqpZ0xZBk5wjIWdgEol2GmVRM8QGbeuakU/HVz5y | |||||
| lPzxUUocgdbSi3GE3zbzijQzVJdyL/kw/KP7pKT/PPKKJ2C5NQDLy0XGKEHddXGR | |||||
| EWKkVlRalxq/TjfaMR0bi3MpezBsQmp99ATPO/d7trayZUxQHRtXzGFiOXfDHATr | |||||
| qN730sODjqvU+mpc/SHCRwh9qWDjZRHSuKU5YDBjb5jIQJivZsQ/ABEBAAGJATYE | |||||
| GAEIACAWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9VQIbDAAKCRD9+v0I6RSE | |||||
| H7WoB/4tXl+97rQ6owPCGSVp1Xbwt2521V7COgsOFRVTRTryEWxRW8mm0S7wQvax | |||||
| C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 | |||||
| 21dccpqchByVw/UMDeHSyjQLiG2lxzt8Gfx2gHmSbrq3aWovTGyz6JTffZvfy/n2 | |||||
| 0Hm437OBPazO0gZyXhdV2PE5RSUfvAgm44235tcV5EV0d32TJDfv61+Vr2GUbah6 | |||||
| 7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M | |||||
| GrE0MHOxUbc9tbtyk0F1SuzREUBH | |||||
| =DDXw | |||||
| -----END PGP PUBLIC KEY BLOCK----- | |||||
| `) | |||||
| //Export new key | |||||
| testExportUserGPGKeys(t, "user1", `-----BEGIN PGP PUBLIC KEY BLOCK----- | |||||
| xsBNBFyy/VUBCADJ7zbM20Z1RWmFoVgp5WkQfI2rU1Vj9cQHes9i42wVLLtcbPeo | |||||
| QzubgzvMPITDy7nfWxgSf83E23DoHQ1ACFbQh/6eFSRrjsusp3YQ/08NSfPPbcu8 | |||||
| 0M5G+VGwSfzS5uEcwBVQmHyKdcOZIERTNMtYZx1C3bjLD1XVJHvWz9D72Uq4qeO3 | |||||
| 8SR+lzp5n6ppUakcmRnxt3nGRBj1+hEGkdgzyPo93iy+WioegY2lwCA9xMEo5dah | |||||
| BmYxWx51zyiXYlReTaxlyb3/nuSUt8IcW3Q8zjdtJj4Nu8U1SpV8EdaA1I9IPbHW | |||||
| 510OSLmD3XhqHH5m6mIxL1YoWxk3V7gpDROtABEBAAHNGVVzZXIxIDx1c2VyMUBl | |||||
| eGFtcGxlLmNvbT7CwI4EEwEIADgWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9 | |||||
| VQIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD9+v0I6RSEH22YCACFqL5+ | |||||
| 6M0m18AMC/pumcpnnmvAS1GrrKTF8nOROA1augZwp1WCNuKw2R6uOJIHANrYECSn | |||||
| u7+j6GBP2gbIW8mSAzS6HWCs7GGiPpVtT4wcu8wljUI6BxjpyZtoEkriyBjt6HfK | |||||
| rkegbkuySoJvjq4IcO5D1LB1JWgsUjMYQJj/ZpBIzVtjG9QtFSOiT1Hct4PoZHdC | |||||
| nsdSgyCkwRZXG+u3kT/wP9F663ba4o16vYlz3dCGo66lF2tyoG3qcyZ1OUzUrnuv | |||||
| 96ytAzT6XIhrE0nVoBprMxFF5zExotJD3bHjcGBFNLf944bhjKee3U6t9+OsfJVC | |||||
| l7N5xxIawCuTQdbfzsBNBFyy/VUBCADe61yGEoTwKfsOKIhxLaNoRmD883O0tiWt | |||||
| soO/HPj9dPQLTOiwXgSgSCd8C+LNxGKct87wgFozpah4tDLC6c0nALuHJ0SLbkfz | |||||
| 55aRhLeOOcrAydatDp72GroXzqpZ0xZBk5wjIWdgEol2GmVRM8QGbeuakU/HVz5y | |||||
| lPzxUUocgdbSi3GE3zbzijQzVJdyL/kw/KP7pKT/PPKKJ2C5NQDLy0XGKEHddXGR | |||||
| EWKkVlRalxq/TjfaMR0bi3MpezBsQmp99ATPO/d7trayZUxQHRtXzGFiOXfDHATr | |||||
| qN730sODjqvU+mpc/SHCRwh9qWDjZRHSuKU5YDBjb5jIQJivZsQ/ABEBAAHCwHYE | |||||
| GAEIACAWIQTQEbrYxmXsp1z3j7z9+v0I6RSEHwUCXLL9VQIbDAAKCRD9+v0I6RSE | |||||
| H7WoB/4tXl+97rQ6owPCGSVp1Xbwt2521V7COgsOFRVTRTryEWxRW8mm0S7wQvax | |||||
| C0TLXKur6NVYQMn01iyL+FZzRpEWNuYF3f9QeeLJ/+l2DafESNhNTy17+RPmacK6 | |||||
| 21dccpqchByVw/UMDeHSyjQLiG2lxzt8Gfx2gHmSbrq3aWovTGyz6JTffZvfy/n2 | |||||
| 0Hm437OBPazO0gZyXhdV2PE5RSUfvAgm44235tcV5EV0d32TJDfv61+Vr2GUbah6 | |||||
| 7XhJ1v6JYuh8kaYaEz8OpZDeh7f6Ho6PzJrsy/TKTKhGgZNINj1iaPFyOkQgKR5M | |||||
| GrE0MHOxUbc9tbtyk0F1SuzREUBH | |||||
| =WFf5 | |||||
| -----END PGP PUBLIC KEY BLOCK----- | |||||
| `) | |||||
| } | |||||
| func testExportUserGPGKeys(t *testing.T, user, expected string) { | |||||
| session := loginUser(t, user) | |||||
| t.Logf("Testing username %s export gpg keys", user) | |||||
| req := NewRequest(t, "GET", "/"+user+".gpg") | |||||
| resp := session.MakeRequest(t, req, http.StatusOK) | |||||
| //t.Log(resp.Body.String()) | |||||
| assert.Equal(t, expected, resp.Body.String()) | |||||
| } | |||||
| @@ -379,6 +379,21 @@ func (err ErrGPGKeyNotExist) Error() string { | |||||
| return fmt.Sprintf("public gpg key does not exist [id: %d]", err.ID) | return fmt.Sprintf("public gpg key does not exist [id: %d]", err.ID) | ||||
| } | } | ||||
| // ErrGPGKeyImportNotExist represents a "GPGKeyImportNotExist" kind of error. | |||||
| type ErrGPGKeyImportNotExist struct { | |||||
| ID string | |||||
| } | |||||
| // IsErrGPGKeyImportNotExist checks if an error is a ErrGPGKeyImportNotExist. | |||||
| func IsErrGPGKeyImportNotExist(err error) bool { | |||||
| _, ok := err.(ErrGPGKeyImportNotExist) | |||||
| return ok | |||||
| } | |||||
| func (err ErrGPGKeyImportNotExist) Error() string { | |||||
| return fmt.Sprintf("public gpg key import does not exist [id: %s]", err.ID) | |||||
| } | |||||
| // ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error. | // ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error. | ||||
| type ErrGPGKeyIDAlreadyUsed struct { | type ErrGPGKeyIDAlreadyUsed struct { | ||||
| KeyID string | KeyID string | ||||
| @@ -0,0 +1 @@ | |||||
| [] # empty | |||||
| @@ -43,6 +43,12 @@ type GPGKey struct { | |||||
| CanCertify bool | CanCertify bool | ||||
| } | } | ||||
| //GPGKeyImport the original import of key | |||||
| type GPGKeyImport struct { | |||||
| KeyID string `xorm:"pk CHAR(16) NOT NULL"` | |||||
| Content string `xorm:"TEXT NOT NULL"` | |||||
| } | |||||
| // BeforeInsert will be invoked by XORM before inserting a record | // BeforeInsert will be invoked by XORM before inserting a record | ||||
| func (key *GPGKey) BeforeInsert() { | func (key *GPGKey) BeforeInsert() { | ||||
| key.AddedUnix = util.TimeStampNow() | key.AddedUnix = util.TimeStampNow() | ||||
| @@ -74,6 +80,18 @@ func GetGPGKeyByID(keyID int64) (*GPGKey, error) { | |||||
| return key, nil | return key, nil | ||||
| } | } | ||||
| // GetGPGImportByKeyID returns the import public armored key by given KeyID. | |||||
| func GetGPGImportByKeyID(keyID string) (*GPGKeyImport, error) { | |||||
| key := new(GPGKeyImport) | |||||
| has, err := x.ID(keyID).Get(key) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } else if !has { | |||||
| return nil, ErrGPGKeyImportNotExist{keyID} | |||||
| } | |||||
| return key, nil | |||||
| } | |||||
| // checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key. | // checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key. | ||||
| // The function returns the actual public key on success | // The function returns the actual public key on success | ||||
| func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) { | func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) { | ||||
| @@ -84,15 +102,37 @@ func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) { | |||||
| return list[0], nil | return list[0], nil | ||||
| } | } | ||||
| //addGPGKey add key and subkeys to database | |||||
| func addGPGKey(e Engine, key *GPGKey) (err error) { | |||||
| //addGPGKey add key, import and subkeys to database | |||||
| func addGPGKey(e Engine, key *GPGKey, content string) (err error) { | |||||
| //Add GPGKeyImport | |||||
| if _, err = e.Insert(GPGKeyImport{ | |||||
| KeyID: key.KeyID, | |||||
| Content: content, | |||||
| }); err != nil { | |||||
| return err | |||||
| } | |||||
| // Save GPG primary key. | |||||
| if _, err = e.Insert(key); err != nil { | |||||
| return err | |||||
| } | |||||
| // Save GPG subs key. | |||||
| for _, subkey := range key.SubsKey { | |||||
| if err := addGPGSubKey(e, subkey); err != nil { | |||||
| return err | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| //addGPGSubKey add subkeys to database | |||||
| func addGPGSubKey(e Engine, key *GPGKey) (err error) { | |||||
| // Save GPG primary key. | // Save GPG primary key. | ||||
| if _, err = e.Insert(key); err != nil { | if _, err = e.Insert(key); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| // Save GPG subs key. | // Save GPG subs key. | ||||
| for _, subkey := range key.SubsKey { | for _, subkey := range key.SubsKey { | ||||
| if err := addGPGKey(e, subkey); err != nil { | |||||
| if err := addGPGSubKey(e, subkey); err != nil { | |||||
| return err | return err | ||||
| } | } | ||||
| } | } | ||||
| @@ -127,14 +167,14 @@ func AddGPGKey(ownerID int64, content string) (*GPGKey, error) { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| if err = addGPGKey(sess, key); err != nil { | |||||
| if err = addGPGKey(sess, key, content); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| return key, sess.Commit() | return key, sess.Commit() | ||||
| } | } | ||||
| //base64EncPubKey encode public kay content to base 64 | |||||
| //base64EncPubKey encode public key content to base 64 | |||||
| func base64EncPubKey(pubkey *packet.PublicKey) (string, error) { | func base64EncPubKey(pubkey *packet.PublicKey) (string, error) { | ||||
| var w bytes.Buffer | var w bytes.Buffer | ||||
| err := pubkey.Serialize(&w) | err := pubkey.Serialize(&w) | ||||
| @@ -144,6 +184,34 @@ func base64EncPubKey(pubkey *packet.PublicKey) (string, error) { | |||||
| return base64.StdEncoding.EncodeToString(w.Bytes()), nil | return base64.StdEncoding.EncodeToString(w.Bytes()), nil | ||||
| } | } | ||||
| //base64DecPubKey decode public key content from base 64 | |||||
| func base64DecPubKey(content string) (*packet.PublicKey, error) { | |||||
| b, err := readerFromBase64(content) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| //Read key | |||||
| p, err := packet.Read(b) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| //Check type | |||||
| pkey, ok := p.(*packet.PublicKey) | |||||
| if !ok { | |||||
| return nil, fmt.Errorf("key is not a public key") | |||||
| } | |||||
| return pkey, nil | |||||
| } | |||||
| //GPGKeyToEntity retrieve the imported key and the traducted entity | |||||
| func GPGKeyToEntity(k *GPGKey) (*openpgp.Entity, error) { | |||||
| impKey, err := GetGPGImportByKeyID(k.KeyID) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return checkArmoredGPGKeyString(impKey.Content) | |||||
| } | |||||
| //parseSubGPGKey parse a sub Key | //parseSubGPGKey parse a sub Key | ||||
| func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) { | func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) { | ||||
| content, err := base64EncPubKey(pubkey) | content, err := base64EncPubKey(pubkey) | ||||
| @@ -244,6 +312,11 @@ func deleteGPGKey(e *xorm.Session, keyID string) (int64, error) { | |||||
| if keyID == "" { | if keyID == "" { | ||||
| return 0, fmt.Errorf("empty KeyId forbidden") //Should never happen but just to be sure | return 0, fmt.Errorf("empty KeyId forbidden") //Should never happen but just to be sure | ||||
| } | } | ||||
| //Delete imported key | |||||
| n, err := e.Where("key_id=?", keyID).Delete(new(GPGKeyImport)) | |||||
| if err != nil { | |||||
| return n, err | |||||
| } | |||||
| return e.Where("key_id=?", keyID).Or("primary_key_id=?", keyID).Delete(new(GPGKey)) | return e.Where("key_id=?", keyID).Or("primary_key_id=?", keyID).Delete(new(GPGKey)) | ||||
| } | } | ||||
| @@ -339,22 +412,10 @@ func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error { | |||||
| return fmt.Errorf("key can not sign") | return fmt.Errorf("key can not sign") | ||||
| } | } | ||||
| //Decode key | //Decode key | ||||
| b, err := readerFromBase64(k.Content) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| //Read key | |||||
| p, err := packet.Read(b) | |||||
| pkey, err := base64DecPubKey(k.Content) | |||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| //Check type | |||||
| pkey, ok := p.(*packet.PublicKey) | |||||
| if !ok { | |||||
| return fmt.Errorf("key is not a public key") | |||||
| } | |||||
| return pkey.VerifySignature(h, s) | return pkey.VerifySignature(h, s) | ||||
| } | } | ||||
| @@ -221,6 +221,8 @@ var migrations = []Migration{ | |||||
| NewMigration("hot fix for wrong release sha1 on release table", fixReleaseSha1OnReleaseTable), | NewMigration("hot fix for wrong release sha1 on release table", fixReleaseSha1OnReleaseTable), | ||||
| // v83 -> v84 | // v83 -> v84 | ||||
| NewMigration("add uploader id for table attachment", addUploaderIDForAttachment), | NewMigration("add uploader id for table attachment", addUploaderIDForAttachment), | ||||
| // v84 -> v85 | |||||
| NewMigration("add table to store original imported gpg keys", addGPGKeyImport), | |||||
| } | } | ||||
| // Migrate database to current version | // Migrate database to current version | ||||
| @@ -0,0 +1,18 @@ | |||||
| // Copyright 2019 The Gitea Authors. All rights reserved. | |||||
| // Use of this source code is governed by a MIT-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package migrations | |||||
| import ( | |||||
| "github.com/go-xorm/xorm" | |||||
| ) | |||||
| func addGPGKeyImport(x *xorm.Engine) error { | |||||
| type GPGKeyImport struct { | |||||
| KeyID string `xorm:"pk CHAR(16) NOT NULL"` | |||||
| Content string `xorm:"TEXT NOT NULL"` | |||||
| } | |||||
| return x.Sync2(new(GPGKeyImport)) | |||||
| } | |||||
| @@ -108,6 +108,7 @@ func init() { | |||||
| new(LFSMetaObject), | new(LFSMetaObject), | ||||
| new(TwoFactor), | new(TwoFactor), | ||||
| new(GPGKey), | new(GPGKey), | ||||
| new(GPGKeyImport), | |||||
| new(RepoUnit), | new(RepoUnit), | ||||
| new(RepoRedirect), | new(RepoRedirect), | ||||
| new(ExternalLoginUser), | new(ExternalLoginUser), | ||||
| @@ -747,7 +747,7 @@ var ( | |||||
| ".", | ".", | ||||
| "..", | "..", | ||||
| } | } | ||||
| reservedUserPatterns = []string{"*.keys"} | |||||
| reservedUserPatterns = []string{"*.keys", "*.gpg"} | |||||
| ) | ) | ||||
| // isUsableName checks if name is reserved or pattern of name is not allowed | // isUsableName checks if name is reserved or pattern of name is not allowed | ||||
| @@ -19,6 +19,8 @@ import ( | |||||
| "github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
| "github.com/Unknwon/paginater" | "github.com/Unknwon/paginater" | ||||
| "github.com/keybase/go-crypto/openpgp" | |||||
| "github.com/keybase/go-crypto/openpgp/armor" | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| @@ -384,6 +386,45 @@ func ShowSSHKeys(ctx *context.Context, uid int64) { | |||||
| ctx.PlainText(200, buf.Bytes()) | ctx.PlainText(200, buf.Bytes()) | ||||
| } | } | ||||
| // ShowGPGKeys output all the public GPG keys of user by uid | |||||
| func ShowGPGKeys(ctx *context.Context, uid int64) { | |||||
| keys, err := models.ListGPGKeys(uid) | |||||
| if err != nil { | |||||
| ctx.ServerError("ListGPGKeys", err) | |||||
| return | |||||
| } | |||||
| entities := make([]*openpgp.Entity, 0) | |||||
| failedEntitiesID := make([]string, 0) | |||||
| for _, k := range keys { | |||||
| e, err := models.GPGKeyToEntity(k) | |||||
| if err != nil { | |||||
| if models.IsErrGPGKeyImportNotExist(err) { | |||||
| failedEntitiesID = append(failedEntitiesID, k.KeyID) | |||||
| continue //Skip previous import without backup of imported armored key | |||||
| } | |||||
| ctx.ServerError("ShowGPGKeys", err) | |||||
| return | |||||
| } | |||||
| entities = append(entities, e) | |||||
| } | |||||
| var buf bytes.Buffer | |||||
| headers := make(map[string]string) | |||||
| if len(failedEntitiesID) > 0 { //If some key need re-import to be exported | |||||
| headers["Note"] = fmt.Sprintf("The keys with the following IDs couldn't be exported and need to be reuploaded %s", strings.Join(failedEntitiesID, ", ")) | |||||
| } | |||||
| writer, _ := armor.Encode(&buf, "PGP PUBLIC KEY BLOCK", headers) | |||||
| for _, e := range entities { | |||||
| err = e.Serialize(writer) //TODO find why key are exported with a different cipherTypeByte as original (should not be blocking but strange) | |||||
| if err != nil { | |||||
| ctx.ServerError("ShowGPGKeys", err) | |||||
| return | |||||
| } | |||||
| } | |||||
| writer.Close() | |||||
| ctx.PlainText(200, buf.Bytes()) | |||||
| } | |||||
| func showOrgProfile(ctx *context.Context) { | func showOrgProfile(ctx *context.Context) { | ||||
| ctx.SetParams(":org", ctx.Params(":username")) | ctx.SetParams(":org", ctx.Params(":username")) | ||||
| context.HandleOrgAssignment(ctx) | context.HandleOrgAssignment(ctx) | ||||
| @@ -59,9 +59,16 @@ func Profile(ctx *context.Context) { | |||||
| isShowKeys := false | isShowKeys := false | ||||
| if strings.HasSuffix(uname, ".keys") { | if strings.HasSuffix(uname, ".keys") { | ||||
| isShowKeys = true | isShowKeys = true | ||||
| uname = strings.TrimSuffix(uname, ".keys") | |||||
| } | } | ||||
| ctxUser := GetUserByName(ctx, strings.TrimSuffix(uname, ".keys")) | |||||
| isShowGPG := false | |||||
| if strings.HasSuffix(uname, ".gpg") { | |||||
| isShowGPG = true | |||||
| uname = strings.TrimSuffix(uname, ".gpg") | |||||
| } | |||||
| ctxUser := GetUserByName(ctx, uname) | |||||
| if ctx.Written() { | if ctx.Written() { | ||||
| return | return | ||||
| } | } | ||||
| @@ -72,6 +79,12 @@ func Profile(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| // Show GPG keys. | |||||
| if isShowGPG { | |||||
| ShowGPGKeys(ctx, ctxUser.ID) | |||||
| return | |||||
| } | |||||
| if ctxUser.IsOrganization() { | if ctxUser.IsOrganization() { | ||||
| showOrgProfile(ctx) | showOrgProfile(ctx) | ||||
| return | return | ||||