2021-07-24 13:16:34 +03:00
// Copyright 2021 The Gitea 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 models
import (
"fmt"
"time"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2021-07-24 13:16:34 +03:00
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
"xorm.io/xorm"
)
// ________ .__ ____ __.
// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
// \/ \/|__| \/ \/ \/\/
//
// 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 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 ( ) error {
pkey , err := GetPublicKeyByID ( 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
func ( key * DeployKey ) IsReadOnly ( ) bool {
return key . Mode == AccessModeRead
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( DeployKey ) )
}
func checkDeployKey ( e db . Engine , keyID , repoID int64 , name string ) error {
2021-07-24 13:16:34 +03:00
// Note: We want error detail, not just true or false here.
has , err := e .
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 = e .
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 ( e * xorm . Session , keyID , repoID int64 , name , fingerprint string , mode AccessMode ) ( * DeployKey , error ) {
if err := checkDeployKey ( e , keyID , repoID , name ) ; err != nil {
return nil , err
}
key := & DeployKey {
KeyID : keyID ,
RepoID : repoID ,
Name : name ,
Fingerprint : fingerprint ,
Mode : mode ,
}
_ , err := e . Insert ( key )
return key , err
}
// HasDeployKey returns true if public key is a deploy key of given repository.
func HasDeployKey ( keyID , repoID int64 ) bool {
2021-09-23 18:45:36 +03:00
has , _ := db . GetEngine ( db . DefaultContext ) .
2021-07-24 13:16:34 +03:00
Where ( "key_id = ? AND repo_id = ?" , keyID , repoID ) .
Get ( new ( DeployKey ) )
return has
}
// AddDeployKey add new deploy key to database and authorized_keys file.
func AddDeployKey ( repoID int64 , name , content string , readOnly bool ) ( * DeployKey , error ) {
fingerprint , err := calcFingerprint ( content )
if err != nil {
return nil , err
}
accessMode := AccessModeRead
if ! readOnly {
accessMode = AccessModeWrite
}
2021-09-23 18:45:36 +03:00
sess := db . NewSession ( db . DefaultContext )
2021-07-24 13:16:34 +03:00
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return nil , err
}
pkey := & PublicKey {
Fingerprint : fingerprint ,
}
has , err := sess . Get ( pkey )
if err != nil {
return nil , err
}
if has {
if pkey . Type != KeyTypeDeploy {
return nil , ErrKeyAlreadyExist { 0 , fingerprint , "" }
}
} else {
// First time use this deploy key.
pkey . Mode = accessMode
pkey . Type = KeyTypeDeploy
pkey . Content = content
pkey . Name = name
if err = addKey ( sess , pkey ) ; err != nil {
return nil , fmt . Errorf ( "addKey: %v" , err )
}
}
key , err := addDeployKey ( sess , pkey . ID , repoID , name , pkey . Fingerprint , accessMode )
if err != nil {
return nil , err
}
return key , sess . Commit ( )
}
// GetDeployKeyByID returns deploy key by given ID.
func GetDeployKeyByID ( id int64 ) ( * DeployKey , error ) {
2021-09-23 18:45:36 +03:00
return getDeployKeyByID ( db . GetEngine ( db . DefaultContext ) , id )
2021-07-24 13:16:34 +03:00
}
2021-09-19 14:49:59 +03:00
func getDeployKeyByID ( e db . Engine , id int64 ) ( * DeployKey , error ) {
2021-07-24 13:16:34 +03:00
key := new ( DeployKey )
has , err := e . ID ( id ) . Get ( key )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrDeployKeyNotExist { id , 0 , 0 }
}
return key , nil
}
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
func GetDeployKeyByRepo ( keyID , repoID int64 ) ( * DeployKey , error ) {
2021-09-23 18:45:36 +03:00
return getDeployKeyByRepo ( db . GetEngine ( db . DefaultContext ) , keyID , repoID )
2021-07-24 13:16:34 +03:00
}
2021-09-19 14:49:59 +03:00
func getDeployKeyByRepo ( e db . Engine , keyID , repoID int64 ) ( * DeployKey , error ) {
2021-07-24 13:16:34 +03:00
key := & DeployKey {
KeyID : keyID ,
RepoID : repoID ,
}
has , err := e . Get ( key )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrDeployKeyNotExist { 0 , keyID , repoID }
}
return key , nil
}
// UpdateDeployKeyCols updates deploy key information in the specified columns.
func UpdateDeployKeyCols ( key * DeployKey , cols ... string ) error {
2021-09-23 18:45:36 +03:00
_ , err := db . GetEngine ( db . DefaultContext ) . ID ( key . ID ) . Cols ( cols ... ) . Update ( key )
2021-07-24 13:16:34 +03:00
return err
}
// UpdateDeployKey updates deploy key information.
func UpdateDeployKey ( key * DeployKey ) error {
2021-09-23 18:45:36 +03:00
_ , err := db . GetEngine ( db . DefaultContext ) . ID ( key . ID ) . AllCols ( ) . Update ( key )
2021-07-24 13:16:34 +03:00
return err
}
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
func DeleteDeployKey ( doer * User , id int64 ) error {
2021-09-23 18:45:36 +03:00
sess := db . NewSession ( db . DefaultContext )
2021-07-24 13:16:34 +03:00
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return err
}
if err := deleteDeployKey ( sess , doer , id ) ; err != nil {
return err
}
return sess . Commit ( )
}
2021-09-19 14:49:59 +03:00
func deleteDeployKey ( sess db . Engine , doer * User , id int64 ) error {
2021-07-24 13:16:34 +03:00
key , err := getDeployKeyByID ( sess , id )
if err != nil {
if IsErrDeployKeyNotExist ( err ) {
return nil
}
return fmt . Errorf ( "GetDeployKeyByID: %v" , err )
}
// Check if user has access to delete this key.
if ! doer . IsAdmin {
repo , err := getRepositoryByID ( sess , key . RepoID )
if err != nil {
return fmt . Errorf ( "GetRepositoryByID: %v" , err )
}
has , err := isUserRepoAdmin ( sess , repo , doer )
if err != nil {
return fmt . Errorf ( "GetUserRepoPermission: %v" , err )
} else if ! has {
return ErrKeyAccessDenied { doer . ID , key . ID , "deploy" }
}
}
if _ , err = sess . ID ( key . ID ) . Delete ( new ( DeployKey ) ) ; err != nil {
return fmt . Errorf ( "delete deploy key [%d]: %v" , key . ID , err )
}
// Check if this is the last reference to same key content.
has , err := sess .
Where ( "key_id = ?" , key . KeyID ) .
Get ( new ( DeployKey ) )
if err != nil {
return err
} else if ! has {
if err = deletePublicKeys ( sess , key . KeyID ) ; err != nil {
return err
}
// after deleted the public keys, should rewrite the public keys file
if err = rewriteAllPublicKeys ( sess ) ; err != nil {
return err
}
}
return nil
}
2021-08-12 15:43:08 +03:00
// ListDeployKeysOptions are options for ListDeployKeys
type ListDeployKeysOptions struct {
2021-09-24 14:32:56 +03:00
db . ListOptions
2021-08-12 15:43:08 +03:00
RepoID int64
KeyID int64
Fingerprint string
}
func ( opt ListDeployKeysOptions ) toCond ( ) 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
2021-07-24 13:16:34 +03:00
}
2021-08-12 15:43:08 +03:00
// ListDeployKeys returns a list of deploy keys matching the provided arguments.
func ListDeployKeys ( opts * ListDeployKeysOptions ) ( [ ] * DeployKey , error ) {
2021-09-23 18:45:36 +03:00
return listDeployKeys ( db . GetEngine ( db . DefaultContext ) , opts )
2021-08-12 15:43:08 +03:00
}
2021-07-24 13:16:34 +03:00
2021-09-19 14:49:59 +03:00
func listDeployKeys ( e db . Engine , opts * ListDeployKeysOptions ) ( [ ] * DeployKey , error ) {
2021-08-12 15:43:08 +03:00
sess := e . Where ( opts . toCond ( ) )
if opts . Page != 0 {
2021-09-24 14:32:56 +03:00
sess = db . SetSessionPagination ( sess , opts )
2021-08-12 15:43:08 +03:00
keys := make ( [ ] * DeployKey , 0 , opts . PageSize )
2021-07-24 13:16:34 +03:00
return keys , sess . Find ( & keys )
}
keys := make ( [ ] * DeployKey , 0 , 5 )
return keys , sess . Find ( & keys )
}
2021-08-12 15:43:08 +03:00
// CountDeployKeys returns count deploy keys matching the provided arguments.
func CountDeployKeys ( opts * ListDeployKeysOptions ) ( int64 , error ) {
2021-09-23 18:45:36 +03:00
return db . GetEngine ( db . DefaultContext ) . Where ( opts . toCond ( ) ) . Count ( & DeployKey { } )
2021-07-24 13:16:34 +03:00
}