code.gitea.io/gitea@v1.22.3/models/asymkey/ssh_key_deploy.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package asymkey
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"time"
    10  
    11  	"code.gitea.io/gitea/models/db"
    12  	"code.gitea.io/gitea/models/perm"
    13  	"code.gitea.io/gitea/modules/timeutil"
    14  
    15  	"xorm.io/builder"
    16  )
    17  
    18  // ________                .__                 ____  __.
    19  // \______ \   ____ ______ |  |   ____ ___.__.|    |/ _|____ ___.__.
    20  //  |    |  \_/ __ \\____ \|  |  /  _ <   |  ||      <_/ __ <   |  |
    21  //  |    `   \  ___/|  |_> >  |_(  <_> )___  ||    |  \  ___/\___  |
    22  // /_______  /\___  >   __/|____/\____// ____||____|__ \___  > ____|
    23  //         \/     \/|__|               \/             \/   \/\/
    24  //
    25  // This file contains functions specific to DeployKeys
    26  
    27  // DeployKey represents deploy key information and its relation with repository.
    28  type DeployKey struct {
    29  	ID          int64 `xorm:"pk autoincr"`
    30  	KeyID       int64 `xorm:"UNIQUE(s) INDEX"`
    31  	RepoID      int64 `xorm:"UNIQUE(s) INDEX"`
    32  	Name        string
    33  	Fingerprint string
    34  	Content     string `xorm:"-"`
    35  
    36  	Mode perm.AccessMode `xorm:"NOT NULL DEFAULT 1"`
    37  
    38  	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
    39  	UpdatedUnix       timeutil.TimeStamp `xorm:"updated"`
    40  	HasRecentActivity bool               `xorm:"-"`
    41  	HasUsed           bool               `xorm:"-"`
    42  }
    43  
    44  // AfterLoad is invoked from XORM after setting the values of all fields of this object.
    45  func (key *DeployKey) AfterLoad() {
    46  	key.HasUsed = key.UpdatedUnix > key.CreatedUnix
    47  	key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
    48  }
    49  
    50  // GetContent gets associated public key content.
    51  func (key *DeployKey) GetContent(ctx context.Context) error {
    52  	pkey, err := GetPublicKeyByID(ctx, key.KeyID)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	key.Content = pkey.Content
    57  	return nil
    58  }
    59  
    60  // IsReadOnly checks if the key can only be used for read operations, used by template
    61  func (key *DeployKey) IsReadOnly() bool {
    62  	return key.Mode == perm.AccessModeRead
    63  }
    64  
    65  func init() {
    66  	db.RegisterModel(new(DeployKey))
    67  }
    68  
    69  func checkDeployKey(ctx context.Context, keyID, repoID int64, name string) error {
    70  	// Note: We want error detail, not just true or false here.
    71  	has, err := db.GetEngine(ctx).
    72  		Where("key_id = ? AND repo_id = ?", keyID, repoID).
    73  		Get(new(DeployKey))
    74  	if err != nil {
    75  		return err
    76  	} else if has {
    77  		return ErrDeployKeyAlreadyExist{keyID, repoID}
    78  	}
    79  
    80  	has, err = db.GetEngine(ctx).
    81  		Where("repo_id = ? AND name = ?", repoID, name).
    82  		Get(new(DeployKey))
    83  	if err != nil {
    84  		return err
    85  	} else if has {
    86  		return ErrDeployKeyNameAlreadyUsed{repoID, name}
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  // addDeployKey adds new key-repo relation.
    93  func addDeployKey(ctx context.Context, keyID, repoID int64, name, fingerprint string, mode perm.AccessMode) (*DeployKey, error) {
    94  	if err := checkDeployKey(ctx, keyID, repoID, name); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	key := &DeployKey{
    99  		KeyID:       keyID,
   100  		RepoID:      repoID,
   101  		Name:        name,
   102  		Fingerprint: fingerprint,
   103  		Mode:        mode,
   104  	}
   105  	return key, db.Insert(ctx, key)
   106  }
   107  
   108  // HasDeployKey returns true if public key is a deploy key of given repository.
   109  func HasDeployKey(ctx context.Context, keyID, repoID int64) bool {
   110  	has, _ := db.GetEngine(ctx).
   111  		Where("key_id = ? AND repo_id = ?", keyID, repoID).
   112  		Get(new(DeployKey))
   113  	return has
   114  }
   115  
   116  // AddDeployKey add new deploy key to database and authorized_keys file.
   117  func AddDeployKey(ctx context.Context, repoID int64, name, content string, readOnly bool) (*DeployKey, error) {
   118  	fingerprint, err := CalcFingerprint(content)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	accessMode := perm.AccessModeRead
   124  	if !readOnly {
   125  		accessMode = perm.AccessModeWrite
   126  	}
   127  
   128  	ctx, committer, err := db.TxContext(ctx)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	defer committer.Close()
   133  
   134  	pkey, exist, err := db.Get[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint})
   135  	if err != nil {
   136  		return nil, err
   137  	} else if exist {
   138  		if pkey.Type != KeyTypeDeploy {
   139  			return nil, ErrKeyAlreadyExist{0, fingerprint, ""}
   140  		}
   141  	} else {
   142  		// First time use this deploy key.
   143  		pkey = &PublicKey{
   144  			Fingerprint: fingerprint,
   145  			Mode:        accessMode,
   146  			Type:        KeyTypeDeploy,
   147  			Content:     content,
   148  			Name:        name,
   149  		}
   150  		if err = addKey(ctx, pkey); err != nil {
   151  			return nil, fmt.Errorf("addKey: %w", err)
   152  		}
   153  	}
   154  
   155  	key, err := addDeployKey(ctx, pkey.ID, repoID, name, pkey.Fingerprint, accessMode)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return key, committer.Commit()
   161  }
   162  
   163  // GetDeployKeyByID returns deploy key by given ID.
   164  func GetDeployKeyByID(ctx context.Context, id int64) (*DeployKey, error) {
   165  	key, exist, err := db.GetByID[DeployKey](ctx, id)
   166  	if err != nil {
   167  		return nil, err
   168  	} else if !exist {
   169  		return nil, ErrDeployKeyNotExist{id, 0, 0}
   170  	}
   171  	return key, nil
   172  }
   173  
   174  // GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
   175  func GetDeployKeyByRepo(ctx context.Context, keyID, repoID int64) (*DeployKey, error) {
   176  	key, exist, err := db.Get[DeployKey](ctx, builder.Eq{"key_id": keyID, "repo_id": repoID})
   177  	if err != nil {
   178  		return nil, err
   179  	} else if !exist {
   180  		return nil, ErrDeployKeyNotExist{0, keyID, repoID}
   181  	}
   182  	return key, nil
   183  }
   184  
   185  // IsDeployKeyExistByKeyID return true if there is at least one deploykey with the key id
   186  func IsDeployKeyExistByKeyID(ctx context.Context, keyID int64) (bool, error) {
   187  	return db.GetEngine(ctx).
   188  		Where("key_id = ?", keyID).
   189  		Get(new(DeployKey))
   190  }
   191  
   192  // UpdateDeployKeyCols updates deploy key information in the specified columns.
   193  func UpdateDeployKeyCols(ctx context.Context, key *DeployKey, cols ...string) error {
   194  	_, err := db.GetEngine(ctx).ID(key.ID).Cols(cols...).Update(key)
   195  	return err
   196  }
   197  
   198  // ListDeployKeysOptions are options for ListDeployKeys
   199  type ListDeployKeysOptions struct {
   200  	db.ListOptions
   201  	RepoID      int64
   202  	KeyID       int64
   203  	Fingerprint string
   204  }
   205  
   206  func (opt ListDeployKeysOptions) ToConds() builder.Cond {
   207  	cond := builder.NewCond()
   208  	if opt.RepoID != 0 {
   209  		cond = cond.And(builder.Eq{"repo_id": opt.RepoID})
   210  	}
   211  	if opt.KeyID != 0 {
   212  		cond = cond.And(builder.Eq{"key_id": opt.KeyID})
   213  	}
   214  	if opt.Fingerprint != "" {
   215  		cond = cond.And(builder.Eq{"fingerprint": opt.Fingerprint})
   216  	}
   217  	return cond
   218  }