gpg_keys.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Copyright 2025 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package database
  5. import (
  6. "context"
  7. "encoding/json"
  8. "time"
  9. "github.com/pkg/errors"
  10. "gorm.io/gorm"
  11. )
  12. // GPGKey represents a GPG key for commit signature verification.
  13. type GPGKey struct {
  14. ID int64 `gorm:"primaryKey"`
  15. OwnerID int64 `gorm:"index;not null"`
  16. KeyID string `gorm:"type:VARCHAR(16);unique;not null"` // Short key ID (8 or 16 chars)
  17. Fingerprint string `gorm:"type:VARCHAR(40);not null"` // Full 40-character fingerprint
  18. Content string `gorm:"type:TEXT;not null"` // ASCII-armored public key
  19. CanSign bool `gorm:"not null;default:false"`
  20. CanEncrypt bool `gorm:"not null;default:false"`
  21. Emails string `gorm:"type:TEXT"` // JSON array of email addresses
  22. Created time.Time `gorm:"-"`
  23. CreatedUnix int64
  24. Updated time.Time `gorm:"-"`
  25. UpdatedUnix int64
  26. Expired time.Time `gorm:"-"`
  27. ExpiredUnix int64
  28. HasRecentActivity bool `gorm:"-" json:"-"`
  29. }
  30. // BeforeCreate implements the GORM create hook.
  31. func (k *GPGKey) BeforeCreate(tx *gorm.DB) error {
  32. if k.CreatedUnix == 0 {
  33. k.CreatedUnix = tx.NowFunc().Unix()
  34. }
  35. if k.UpdatedUnix == 0 {
  36. k.UpdatedUnix = k.CreatedUnix
  37. }
  38. return nil
  39. }
  40. // BeforeUpdate implements the GORM update hook.
  41. func (k *GPGKey) BeforeUpdate(tx *gorm.DB) error {
  42. k.UpdatedUnix = tx.NowFunc().Unix()
  43. return nil
  44. }
  45. // AfterFind implements the GORM query hook.
  46. func (k *GPGKey) AfterFind(tx *gorm.DB) error {
  47. k.Created = time.Unix(k.CreatedUnix, 0).Local()
  48. k.Updated = time.Unix(k.UpdatedUnix, 0).Local()
  49. if k.ExpiredUnix > 0 {
  50. k.Expired = time.Unix(k.ExpiredUnix, 0).Local()
  51. }
  52. return nil
  53. }
  54. // GetEmails returns the list of email addresses associated with this GPG key.
  55. func (k *GPGKey) GetEmails() ([]string, error) {
  56. if k.Emails == "" {
  57. return []string{}, nil
  58. }
  59. var emails []string
  60. err := json.Unmarshal([]byte(k.Emails), &emails)
  61. if err != nil {
  62. return nil, errors.Wrap(err, "unmarshal emails")
  63. }
  64. return emails, nil
  65. }
  66. // SetEmails sets the list of email addresses for this GPG key.
  67. func (k *GPGKey) SetEmails(emails []string) error {
  68. data, err := json.Marshal(emails)
  69. if err != nil {
  70. return errors.Wrap(err, "marshal emails")
  71. }
  72. k.Emails = string(data)
  73. return nil
  74. }
  75. // IsExpired returns true if the key has expired.
  76. func (k *GPGKey) IsExpired() bool {
  77. return k.ExpiredUnix > 0 && time.Now().Unix() > k.ExpiredUnix
  78. }
  79. // GPGKeysStore is the storage layer for GPG keys.
  80. type GPGKeysStore struct {
  81. db *gorm.DB
  82. }
  83. func newGPGKeysStore(db *gorm.DB) *GPGKeysStore {
  84. return &GPGKeysStore{db: db}
  85. }
  86. // Create creates a new GPG key for the given user.
  87. func (s *GPGKeysStore) Create(ctx context.Context, ownerID int64, keyID, fingerprint, content string, emails []string, canSign, canEncrypt bool, expiredUnix int64) (*GPGKey, error) {
  88. key := &GPGKey{
  89. OwnerID: ownerID,
  90. KeyID: keyID,
  91. Fingerprint: fingerprint,
  92. Content: content,
  93. CanSign: canSign,
  94. CanEncrypt: canEncrypt,
  95. ExpiredUnix: expiredUnix,
  96. }
  97. if err := key.SetEmails(emails); err != nil {
  98. return nil, errors.Wrap(err, "set emails")
  99. }
  100. err := s.db.WithContext(ctx).Create(key).Error
  101. if err != nil {
  102. return nil, errors.Wrap(err, "create")
  103. }
  104. return key, nil
  105. }
  106. // GetByID returns the GPG key with the given ID.
  107. func (s *GPGKeysStore) GetByID(ctx context.Context, id int64) (*GPGKey, error) {
  108. var key GPGKey
  109. err := s.db.WithContext(ctx).Where("id = ?", id).First(&key).Error
  110. if err != nil {
  111. if err == gorm.ErrRecordNotFound {
  112. return nil, ErrGPGKeyNotExist{args: map[string]any{"keyID": id}}
  113. }
  114. return nil, errors.Wrap(err, "get")
  115. }
  116. return &key, nil
  117. }
  118. // GetByKeyID returns the GPG key with the given key ID.
  119. func (s *GPGKeysStore) GetByKeyID(ctx context.Context, keyID string) (*GPGKey, error) {
  120. var key GPGKey
  121. err := s.db.WithContext(ctx).Where("key_id = ?", keyID).First(&key).Error
  122. if err != nil {
  123. if err == gorm.ErrRecordNotFound {
  124. return nil, ErrGPGKeyNotExist{args: map[string]any{"keyID": keyID}}
  125. }
  126. return nil, errors.Wrap(err, "get")
  127. }
  128. return &key, nil
  129. }
  130. // GetByFingerprint returns the GPG key with the given fingerprint.
  131. func (s *GPGKeysStore) GetByFingerprint(ctx context.Context, fingerprint string) (*GPGKey, error) {
  132. var key GPGKey
  133. err := s.db.WithContext(ctx).Where("fingerprint = ?", fingerprint).First(&key).Error
  134. if err != nil {
  135. if err == gorm.ErrRecordNotFound {
  136. return nil, ErrGPGKeyNotExist{args: map[string]any{"fingerprint": fingerprint}}
  137. }
  138. return nil, errors.Wrap(err, "get")
  139. }
  140. return &key, nil
  141. }
  142. // List returns all GPG keys for the given user.
  143. func (s *GPGKeysStore) List(ctx context.Context, ownerID int64) ([]*GPGKey, error) {
  144. var keys []*GPGKey
  145. err := s.db.WithContext(ctx).Where("owner_id = ?", ownerID).Order("created_unix DESC").Find(&keys).Error
  146. if err != nil {
  147. return nil, errors.Wrap(err, "list")
  148. }
  149. return keys, nil
  150. }
  151. // Delete deletes the GPG key with the given ID.
  152. func (s *GPGKeysStore) Delete(ctx context.Context, id int64) error {
  153. result := s.db.WithContext(ctx).Where("id = ?", id).Delete(&GPGKey{})
  154. if result.Error != nil {
  155. return errors.Wrap(result.Error, "delete")
  156. }
  157. if result.RowsAffected == 0 {
  158. return ErrGPGKeyNotExist{args: map[string]any{"keyID": id}}
  159. }
  160. return nil
  161. }
  162. // DeleteByOwnerID deletes all GPG keys for the given user.
  163. func (s *GPGKeysStore) DeleteByOwnerID(ctx context.Context, ownerID int64) error {
  164. return s.db.WithContext(ctx).Where("owner_id = ?", ownerID).Delete(&GPGKey{}).Error
  165. }