| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- // Copyright 2025 The Gogs 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 database
- import (
- "context"
- "encoding/json"
- "time"
- "github.com/pkg/errors"
- "gorm.io/gorm"
- )
- // GPGKey represents a GPG key for commit signature verification.
- type GPGKey struct {
- ID int64 `gorm:"primaryKey"`
- OwnerID int64 `gorm:"index;not null"`
- KeyID string `gorm:"type:VARCHAR(16);unique;not null"` // Short key ID (8 or 16 chars)
- Fingerprint string `gorm:"type:VARCHAR(40);not null"` // Full 40-character fingerprint
- Content string `gorm:"type:TEXT;not null"` // ASCII-armored public key
- CanSign bool `gorm:"not null;default:false"`
- CanEncrypt bool `gorm:"not null;default:false"`
- Emails string `gorm:"type:TEXT"` // JSON array of email addresses
- Created time.Time `gorm:"-"`
- CreatedUnix int64
- Updated time.Time `gorm:"-"`
- UpdatedUnix int64
- Expired time.Time `gorm:"-"`
- ExpiredUnix int64
- HasRecentActivity bool `gorm:"-" json:"-"`
- }
- // BeforeCreate implements the GORM create hook.
- func (k *GPGKey) BeforeCreate(tx *gorm.DB) error {
- if k.CreatedUnix == 0 {
- k.CreatedUnix = tx.NowFunc().Unix()
- }
- if k.UpdatedUnix == 0 {
- k.UpdatedUnix = k.CreatedUnix
- }
- return nil
- }
- // BeforeUpdate implements the GORM update hook.
- func (k *GPGKey) BeforeUpdate(tx *gorm.DB) error {
- k.UpdatedUnix = tx.NowFunc().Unix()
- return nil
- }
- // AfterFind implements the GORM query hook.
- func (k *GPGKey) AfterFind(tx *gorm.DB) error {
- k.Created = time.Unix(k.CreatedUnix, 0).Local()
- k.Updated = time.Unix(k.UpdatedUnix, 0).Local()
- if k.ExpiredUnix > 0 {
- k.Expired = time.Unix(k.ExpiredUnix, 0).Local()
- }
- return nil
- }
- // GetEmails returns the list of email addresses associated with this GPG key.
- func (k *GPGKey) GetEmails() ([]string, error) {
- if k.Emails == "" {
- return []string{}, nil
- }
- var emails []string
- err := json.Unmarshal([]byte(k.Emails), &emails)
- if err != nil {
- return nil, errors.Wrap(err, "unmarshal emails")
- }
- return emails, nil
- }
- // SetEmails sets the list of email addresses for this GPG key.
- func (k *GPGKey) SetEmails(emails []string) error {
- data, err := json.Marshal(emails)
- if err != nil {
- return errors.Wrap(err, "marshal emails")
- }
- k.Emails = string(data)
- return nil
- }
- // IsExpired returns true if the key has expired.
- func (k *GPGKey) IsExpired() bool {
- return k.ExpiredUnix > 0 && time.Now().Unix() > k.ExpiredUnix
- }
- // GPGKeysStore is the storage layer for GPG keys.
- type GPGKeysStore struct {
- db *gorm.DB
- }
- func newGPGKeysStore(db *gorm.DB) *GPGKeysStore {
- return &GPGKeysStore{db: db}
- }
- // Create creates a new GPG key for the given user.
- func (s *GPGKeysStore) Create(ctx context.Context, ownerID int64, keyID, fingerprint, content string, emails []string, canSign, canEncrypt bool, expiredUnix int64) (*GPGKey, error) {
- key := &GPGKey{
- OwnerID: ownerID,
- KeyID: keyID,
- Fingerprint: fingerprint,
- Content: content,
- CanSign: canSign,
- CanEncrypt: canEncrypt,
- ExpiredUnix: expiredUnix,
- }
- if err := key.SetEmails(emails); err != nil {
- return nil, errors.Wrap(err, "set emails")
- }
- err := s.db.WithContext(ctx).Create(key).Error
- if err != nil {
- return nil, errors.Wrap(err, "create")
- }
- return key, nil
- }
- // GetByID returns the GPG key with the given ID.
- func (s *GPGKeysStore) GetByID(ctx context.Context, id int64) (*GPGKey, error) {
- var key GPGKey
- err := s.db.WithContext(ctx).Where("id = ?", id).First(&key).Error
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- return nil, ErrGPGKeyNotExist{args: map[string]any{"keyID": id}}
- }
- return nil, errors.Wrap(err, "get")
- }
- return &key, nil
- }
- // GetByKeyID returns the GPG key with the given key ID.
- func (s *GPGKeysStore) GetByKeyID(ctx context.Context, keyID string) (*GPGKey, error) {
- var key GPGKey
- err := s.db.WithContext(ctx).Where("key_id = ?", keyID).First(&key).Error
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- return nil, ErrGPGKeyNotExist{args: map[string]any{"keyID": keyID}}
- }
- return nil, errors.Wrap(err, "get")
- }
- return &key, nil
- }
- // GetByFingerprint returns the GPG key with the given fingerprint.
- func (s *GPGKeysStore) GetByFingerprint(ctx context.Context, fingerprint string) (*GPGKey, error) {
- var key GPGKey
- err := s.db.WithContext(ctx).Where("fingerprint = ?", fingerprint).First(&key).Error
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- return nil, ErrGPGKeyNotExist{args: map[string]any{"fingerprint": fingerprint}}
- }
- return nil, errors.Wrap(err, "get")
- }
- return &key, nil
- }
- // List returns all GPG keys for the given user.
- func (s *GPGKeysStore) List(ctx context.Context, ownerID int64) ([]*GPGKey, error) {
- var keys []*GPGKey
- err := s.db.WithContext(ctx).Where("owner_id = ?", ownerID).Order("created_unix DESC").Find(&keys).Error
- if err != nil {
- return nil, errors.Wrap(err, "list")
- }
- return keys, nil
- }
- // Delete deletes the GPG key with the given ID.
- func (s *GPGKeysStore) Delete(ctx context.Context, id int64) error {
- result := s.db.WithContext(ctx).Where("id = ?", id).Delete(&GPGKey{})
- if result.Error != nil {
- return errors.Wrap(result.Error, "delete")
- }
- if result.RowsAffected == 0 {
- return ErrGPGKeyNotExist{args: map[string]any{"keyID": id}}
- }
- return nil
- }
- // DeleteByOwnerID deletes all GPG keys for the given user.
- func (s *GPGKeysStore) DeleteByOwnerID(ctx context.Context, ownerID int64) error {
- return s.db.WithContext(ctx).Where("owner_id = ?", ownerID).Delete(&GPGKey{}).Error
- }
|