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