add .gpg url (match github behaviour) (#6610)
* 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 tests
This commit is contained in:
		
							parent
							
								
									38889f09cb
								
							
						
					
					
						commit
						d699de32f2
					
				
					 10 changed files with 259 additions and 20 deletions
				
			
		|  | @ -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 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								models/fixtures/gpg_key_import.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								models/fixtures/gpg_key_import.yml
									
									
									
									
									
										Normal file
									
								
							|  | @ -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
 | //addGPGKey add key, import and subkeys to database
 | ||||||
| func addGPGKey(e Engine, key *GPGKey) (err error) { | 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.
 | 	// 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 nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //addGPGSubKey add subkeys to database
 | ||||||
|  | func addGPGSubKey(e Engine, key *GPGKey) (err error) { | ||||||
|  | 	// 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 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) | 	pkey, err := base64DecPubKey(k.Content) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	//Read key
 |  | ||||||
| 	p, err := packet.Read(b) |  | ||||||
| 	if err != nil { |  | ||||||
| 		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
 | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								models/migrations/v84.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								models/migrations/v84.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue