mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
207 lines
5.8 KiB
Go
207 lines
5.8 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package asymkey
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hanzoai/git/models/db"
|
|
"github.com/hanzoai/git/models/perm"
|
|
"github.com/hanzoai/git/modules/timeutil"
|
|
|
|
"github.com/hanzoai/builder"
|
|
)
|
|
|
|
// ________ .__ ____ __.
|
|
// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
|
|
// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
|
|
// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
|
|
// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
|
|
// \/ \/|__| \/ \/ \/\/
|
|
//
|
|
// This file contains functions specific to DeployKeys
|
|
|
|
// DeployKey represents deploy key information and its relation with repository.
|
|
type DeployKey struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
KeyID int64 `xorm:"UNIQUE(s) INDEX"`
|
|
RepoID int64 `xorm:"UNIQUE(s) INDEX"`
|
|
Name string
|
|
Fingerprint string
|
|
Content string `xorm:"-"`
|
|
|
|
Mode perm.AccessMode `xorm:"NOT NULL DEFAULT 1"`
|
|
|
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
|
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
|
HasRecentActivity bool `xorm:"-"`
|
|
HasUsed bool `xorm:"-"`
|
|
}
|
|
|
|
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
|
|
func (key *DeployKey) AfterLoad() {
|
|
key.HasUsed = key.UpdatedUnix > key.CreatedUnix
|
|
key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
|
|
}
|
|
|
|
// GetContent gets associated public key content.
|
|
func (key *DeployKey) GetContent(ctx context.Context) error {
|
|
pkey, err := GetPublicKeyByID(ctx, key.KeyID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key.Content = pkey.Content
|
|
return nil
|
|
}
|
|
|
|
// IsReadOnly checks if the key can only be used for read operations, used by template
|
|
func (key *DeployKey) IsReadOnly() bool {
|
|
return key.Mode == perm.AccessModeRead
|
|
}
|
|
|
|
func init() {
|
|
db.RegisterModel(new(DeployKey))
|
|
}
|
|
|
|
func checkDeployKey(ctx context.Context, keyID, repoID int64, name string) error {
|
|
// Note: We want error detail, not just true or false here.
|
|
has, err := db.GetEngine(ctx).
|
|
Where("key_id = ? AND repo_id = ?", keyID, repoID).
|
|
Get(new(DeployKey))
|
|
if err != nil {
|
|
return err
|
|
} else if has {
|
|
return ErrDeployKeyAlreadyExist{keyID, repoID}
|
|
}
|
|
|
|
has, err = db.GetEngine(ctx).
|
|
Where("repo_id = ? AND name = ?", repoID, name).
|
|
Get(new(DeployKey))
|
|
if err != nil {
|
|
return err
|
|
} else if has {
|
|
return ErrDeployKeyNameAlreadyUsed{repoID, name}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// addDeployKey adds new key-repo relation.
|
|
func addDeployKey(ctx context.Context, keyID, repoID int64, name, fingerprint string, mode perm.AccessMode) (*DeployKey, error) {
|
|
if err := checkDeployKey(ctx, keyID, repoID, name); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key := &DeployKey{
|
|
KeyID: keyID,
|
|
RepoID: repoID,
|
|
Name: name,
|
|
Fingerprint: fingerprint,
|
|
Mode: mode,
|
|
}
|
|
return key, db.Insert(ctx, key)
|
|
}
|
|
|
|
// AddDeployKey add new deploy key to database and authorized_keys file.
|
|
func AddDeployKey(ctx context.Context, repoID int64, name, content string, readOnly bool) (*DeployKey, error) {
|
|
fingerprint, err := CalcFingerprint(content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
accessMode := perm.AccessModeRead
|
|
if !readOnly {
|
|
accessMode = perm.AccessModeWrite
|
|
}
|
|
|
|
return db.WithTx2(ctx, func(ctx context.Context) (*DeployKey, error) {
|
|
pkey, exist, err := db.Get[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint})
|
|
if err != nil {
|
|
return nil, err
|
|
} else if exist {
|
|
if pkey.Type != KeyTypeDeploy {
|
|
return nil, ErrKeyAlreadyExist{0, fingerprint, ""}
|
|
}
|
|
} else {
|
|
// First time use this deploy key.
|
|
pkey = &PublicKey{
|
|
Fingerprint: fingerprint,
|
|
Mode: accessMode,
|
|
Type: KeyTypeDeploy,
|
|
Content: content,
|
|
Name: name,
|
|
}
|
|
if err = addKey(ctx, pkey); err != nil {
|
|
return nil, fmt.Errorf("addKey: %w", err)
|
|
}
|
|
}
|
|
|
|
key, err := addDeployKey(ctx, pkey.ID, repoID, name, pkey.Fingerprint, accessMode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return key, nil
|
|
})
|
|
}
|
|
|
|
// GetDeployKeyByID returns deploy key by given ID.
|
|
func GetDeployKeyByID(ctx context.Context, id int64) (*DeployKey, error) {
|
|
key, exist, err := db.GetByID[DeployKey](ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if !exist {
|
|
return nil, ErrDeployKeyNotExist{id, 0, 0}
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
|
|
func GetDeployKeyByRepo(ctx context.Context, keyID, repoID int64) (*DeployKey, error) {
|
|
key, exist, err := db.Get[DeployKey](ctx, builder.Eq{"key_id": keyID, "repo_id": repoID})
|
|
if err != nil {
|
|
return nil, err
|
|
} else if !exist {
|
|
return nil, ErrDeployKeyNotExist{0, keyID, repoID}
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// IsDeployKeyExistByKeyID return true if there is at least one deploykey with the key id
|
|
func IsDeployKeyExistByKeyID(ctx context.Context, keyID int64) (bool, error) {
|
|
return db.GetEngine(ctx).
|
|
Where("key_id = ?", keyID).
|
|
Get(new(DeployKey))
|
|
}
|
|
|
|
// UpdateDeployKeyCols updates deploy key information in the specified columns.
|
|
func UpdateDeployKeyCols(ctx context.Context, key *DeployKey, cols ...string) error {
|
|
_, err := db.GetEngine(ctx).ID(key.ID).Cols(cols...).Update(key)
|
|
return err
|
|
}
|
|
|
|
// ListDeployKeysOptions are options for ListDeployKeys
|
|
type ListDeployKeysOptions struct {
|
|
db.ListOptions
|
|
RepoID int64
|
|
KeyID int64
|
|
Fingerprint string
|
|
}
|
|
|
|
func (opt ListDeployKeysOptions) ToConds() builder.Cond {
|
|
cond := builder.NewCond()
|
|
if opt.RepoID != 0 {
|
|
cond = cond.And(builder.Eq{"repo_id": opt.RepoID})
|
|
}
|
|
if opt.KeyID != 0 {
|
|
cond = cond.And(builder.Eq{"key_id": opt.KeyID})
|
|
}
|
|
if opt.Fingerprint != "" {
|
|
cond = cond.And(builder.Eq{"fingerprint": opt.Fingerprint})
|
|
}
|
|
return cond
|
|
}
|