2014-03-16 13:24:13 +04:00
// Copyright 2014 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.
2014-02-17 19:57:23 +04:00
package models
import (
2014-03-16 13:24:13 +04:00
"bufio"
2015-01-02 16:38:11 +03:00
"encoding/base64"
"encoding/binary"
2014-03-16 13:48:20 +04:00
"errors"
2014-02-17 19:57:23 +04:00
"fmt"
2014-05-07 20:09:30 +04:00
"io"
2014-03-17 22:03:58 +04:00
"io/ioutil"
2014-02-17 19:57:23 +04:00
"os"
2014-03-16 14:16:03 +04:00
"path"
2014-02-17 19:57:23 +04:00
"path/filepath"
2014-03-16 13:24:13 +04:00
"strings"
"sync"
2014-02-17 19:57:23 +04:00
"time"
2014-03-03 00:25:09 +04:00
"github.com/Unknwon/com"
2015-08-06 17:48:11 +03:00
"github.com/go-xorm/xorm"
2014-03-22 22:27:03 +04:00
"github.com/gogits/gogs/modules/log"
2014-06-19 09:08:03 +04:00
"github.com/gogits/gogs/modules/process"
2014-09-16 21:34:09 +04:00
"github.com/gogits/gogs/modules/setting"
2014-02-17 19:57:23 +04:00
)
2014-03-17 22:03:58 +04:00
const (
// "### autogenerated by gitgos, DO NOT EDIT\n"
2015-02-09 13:27:15 +03:00
_TPL_PUBLICK_KEY = ` command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s ` + "\n"
2014-03-17 22:03:58 +04:00
)
2014-03-21 00:04:56 +04:00
var sshOpLocker = sync . Mutex { }
2014-02-25 12:13:47 +04:00
2015-08-06 17:48:11 +03:00
type KeyType int
const (
KEY_TYPE_USER = iota + 1
KEY_TYPE_DEPLOY
)
// PublicKey represents a SSH or deploy key.
2014-02-17 19:57:23 +04:00
type PublicKey struct {
2015-08-06 17:48:11 +03:00
ID int64 ` xorm:"pk autoincr" `
OwnerID int64 ` xorm:"INDEX NOT NULL" `
Name string ` xorm:"NOT NULL" `
Fingerprint string ` xorm:"NOT NULL" `
2015-08-06 17:55:03 +03:00
Content string ` xorm:"TEXT NOT NULL" `
2015-08-06 17:48:11 +03:00
Mode AccessMode ` xorm:"NOT NULL DEFAULT 2" `
Type KeyType ` xorm:"NOT NULL DEFAULT 1" `
Created time . Time ` xorm:"CREATED" `
Updated time . Time // Note: Updated must below Created for AfterSet.
HasRecentActivity bool ` xorm:"-" `
HasUsed bool ` xorm:"-" `
}
func ( k * PublicKey ) AfterSet ( colName string , _ xorm . Cell ) {
switch colName {
case "created" :
k . HasUsed = k . Updated . After ( k . Created )
k . HasRecentActivity = k . Updated . Add ( 7 * 24 * time . Hour ) . After ( time . Now ( ) )
}
2014-02-17 19:57:23 +04:00
}
2014-11-23 10:33:47 +03:00
// OmitEmail returns content of public key but without e-mail address.
func ( k * PublicKey ) OmitEmail ( ) string {
return strings . Join ( strings . Split ( k . Content , " " ) [ : 2 ] , " " )
}
2014-05-07 20:09:30 +04:00
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
func ( key * PublicKey ) GetAuthorizedString ( ) string {
2015-11-09 00:59:56 +03:00
return fmt . Sprintf ( _TPL_PUBLICK_KEY , setting . AppPath , key . ID , setting . CustomConf , key . Content )
2014-02-17 19:57:23 +04:00
}
2015-01-02 16:38:11 +03:00
func extractTypeFromBase64Key ( key string ) ( string , error ) {
b , err := base64 . StdEncoding . DecodeString ( key )
if err != nil || len ( b ) < 4 {
return "" , errors . New ( "Invalid key format" )
}
keyLength := int ( binary . BigEndian . Uint32 ( b ) )
if len ( b ) < 4 + keyLength {
return "" , errors . New ( "Invalid key format" )
}
return string ( b [ 4 : 4 + keyLength ] ) , nil
}
2015-08-06 17:48:11 +03:00
// parseKeyString parses any key string in openssh or ssh2 format to clean openssh string (rfc4253)
func parseKeyString ( content string ) ( string , error ) {
2015-01-02 16:38:11 +03:00
// Transform all legal line endings to a single "\n"
s := strings . Replace ( strings . Replace ( strings . TrimSpace ( content ) , "\r\n" , "\n" , - 1 ) , "\r" , "\n" , - 1 )
lines := strings . Split ( s , "\n" )
var keyType , keyContent , keyComment string
if len ( lines ) == 1 {
// Parse openssh format
2015-09-12 23:58:18 +03:00
parts := strings . SplitN ( lines [ 0 ] , " " , 3 )
2015-01-02 16:38:11 +03:00
switch len ( parts ) {
case 0 :
return "" , errors . New ( "Empty key" )
case 1 :
keyContent = parts [ 0 ]
case 2 :
keyType = parts [ 0 ]
keyContent = parts [ 1 ]
default :
keyType = parts [ 0 ]
keyContent = parts [ 1 ]
keyComment = parts [ 2 ]
}
// If keyType is not given, extract it from content. If given, validate it
if len ( keyType ) == 0 {
if t , err := extractTypeFromBase64Key ( keyContent ) ; err == nil {
keyType = t
} else {
return "" , err
}
} else {
if t , err := extractTypeFromBase64Key ( keyContent ) ; err != nil || keyType != t {
return "" , err
}
}
} else {
// Parse SSH2 file format.
continuationLine := false
for _ , line := range lines {
// Skip lines that:
// 1) are a continuation of the previous line,
// 2) contain ":" as that are comment lines
// 3) contain "-" as that are begin and end tags
if continuationLine || strings . ContainsAny ( line , ":-" ) {
continuationLine = strings . HasSuffix ( line , "\\" )
} else {
keyContent = keyContent + line
}
}
if t , err := extractTypeFromBase64Key ( keyContent ) ; err == nil {
keyType = t
} else {
return "" , err
}
}
return keyType + " " + keyContent + " " + keyComment , nil
}
2014-07-26 08:24:27 +04:00
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
2015-08-06 17:48:11 +03:00
func CheckPublicKeyString ( content string ) ( _ string , err error ) {
content , err = parseKeyString ( content )
if err != nil {
return "" , err
}
2014-11-18 23:13:08 +03:00
content = strings . TrimRight ( content , "\n\r" )
2014-07-26 08:24:27 +04:00
if strings . ContainsAny ( content , "\n\r" ) {
2015-08-06 17:48:11 +03:00
return "" , errors . New ( "only a single line with a single key please" )
2014-07-26 08:24:27 +04:00
}
// write the key to a file…
tmpFile , err := ioutil . TempFile ( os . TempDir ( ) , "keytest" )
if err != nil {
2015-08-06 17:48:11 +03:00
return "" , err
2014-07-26 08:24:27 +04:00
}
tmpPath := tmpFile . Name ( )
defer os . Remove ( tmpPath )
tmpFile . WriteString ( content )
tmpFile . Close ( )
2014-09-16 21:34:09 +04:00
// Check if ssh-keygen recognizes its contents.
2015-11-03 02:54:47 +03:00
stdout , stderr , err := process . Exec ( "CheckPublicKeyString" , "ssh-keygen" , "-lf" , tmpPath )
2014-07-26 08:24:27 +04:00
if err != nil {
2015-11-03 02:54:47 +03:00
return "" , errors . New ( "ssh-keygen -lf: " + stderr )
2014-07-26 08:24:27 +04:00
} else if len ( stdout ) < 2 {
2015-08-06 17:48:11 +03:00
return "" , errors . New ( "ssh-keygen returned not enough output to evaluate the key: " + stdout )
2014-07-26 08:24:27 +04:00
}
2014-09-16 21:34:09 +04:00
// The ssh-keygen in Windows does not print key type, so no need go further.
if setting . IsWindows {
2015-08-06 17:48:11 +03:00
return content , nil
2014-09-16 21:34:09 +04:00
}
2014-07-26 08:24:27 +04:00
sshKeygenOutput := strings . Split ( stdout , " " )
if len ( sshKeygenOutput ) < 4 {
2015-11-19 05:21:47 +03:00
return content , ErrKeyUnableVerify { stdout }
2014-07-26 08:24:27 +04:00
}
2014-09-16 21:34:09 +04:00
// Check if key type and key size match.
2015-03-25 15:59:48 +03:00
if ! setting . Service . DisableMinimumKeySizeCheck {
keySize := com . StrTo ( sshKeygenOutput [ 0 ] ) . MustInt ( )
if keySize == 0 {
2015-08-06 17:48:11 +03:00
return "" , errors . New ( "cannot get key size of the given key" )
2015-03-25 15:59:48 +03:00
}
2015-11-03 02:54:47 +03:00
keyType := strings . Trim ( sshKeygenOutput [ len ( sshKeygenOutput ) - 1 ] , " ()\n" )
2015-10-30 15:53:06 +03:00
if minimumKeySize := setting . Service . MinimumKeySizes [ keyType ] ; minimumKeySize == 0 {
2015-11-02 22:01:19 +03:00
return "" , fmt . Errorf ( "unrecognized public key type: %s" , keyType )
2015-03-25 15:59:48 +03:00
} else if keySize < minimumKeySize {
2015-08-06 17:48:11 +03:00
return "" , fmt . Errorf ( "the minimum accepted size of a public key %s is %d" , keyType , minimumKeySize )
2015-03-25 15:59:48 +03:00
}
2014-07-26 08:24:27 +04:00
}
2015-08-06 17:48:11 +03:00
return content , nil
2014-07-26 08:24:27 +04:00
}
2014-05-07 20:09:30 +04:00
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
2014-12-31 21:07:51 +03:00
func saveAuthorizedKeyFile ( keys ... * PublicKey ) error {
2014-05-07 20:09:30 +04:00
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
2015-12-20 05:43:32 +03:00
fpath := filepath . Join ( setting . SSHRootPath , "authorized_keys" )
2014-05-07 20:09:30 +04:00
f , err := os . OpenFile ( fpath , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0600 )
if err != nil {
return err
}
2014-08-07 13:06:42 +04:00
defer f . Close ( )
2015-02-02 01:21:56 +03:00
2015-03-12 08:15:01 +03:00
fi , err := f . Stat ( )
2014-08-07 12:00:57 +04:00
if err != nil {
return err
}
2014-09-16 16:32:13 +04:00
// FIXME: following command does not support in Windows.
2014-09-16 21:34:09 +04:00
if ! setting . IsWindows {
2015-03-12 08:15:01 +03:00
// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
if fi . Mode ( ) . Perm ( ) > 0600 {
log . Error ( 4 , "authorized_keys file has unusual permission flags: %s - setting to -rw-------" , fi . Mode ( ) . Perm ( ) . String ( ) )
2014-09-16 16:32:13 +04:00
if err = f . Chmod ( 0600 ) ; err != nil {
return err
}
2014-08-07 13:06:42 +04:00
}
2014-08-07 12:00:57 +04:00
}
2014-12-31 21:07:51 +03:00
for _ , key := range keys {
2015-02-02 01:21:56 +03:00
if _ , err = f . WriteString ( key . GetAuthorizedString ( ) ) ; err != nil {
2014-12-31 21:07:51 +03:00
return err
}
}
return nil
2014-05-07 20:09:30 +04:00
}
2015-08-24 22:30:39 +03:00
// checkKeyContent onlys checks if key content has been used as public key,
// it is OK to use same key as deploy key for multiple repositories/users.
2015-08-06 17:48:11 +03:00
func checkKeyContent ( content string ) error {
2015-08-24 22:30:39 +03:00
has , err := x . Get ( & PublicKey {
Content : content ,
Type : KEY_TYPE_USER ,
} )
2014-03-16 14:25:16 +04:00
if err != nil {
return err
} else if has {
2015-08-06 17:48:11 +03:00
return ErrKeyAlreadyExist { 0 , content }
2014-03-16 14:25:16 +04:00
}
2015-08-06 17:48:11 +03:00
return nil
}
2014-03-16 14:25:16 +04:00
2015-08-06 17:48:11 +03:00
func addKey ( e Engine , key * PublicKey ) ( err error ) {
2014-03-16 14:16:03 +04:00
// Calculate fingerprint.
2014-05-07 20:09:30 +04:00
tmpPath := strings . Replace ( path . Join ( os . TempDir ( ) , fmt . Sprintf ( "%d" , time . Now ( ) . Nanosecond ( ) ) ,
2014-03-22 22:27:03 +04:00
"id_rsa.pub" ) , "\\" , "/" , - 1 )
2014-03-16 14:16:03 +04:00
os . MkdirAll ( path . Dir ( tmpPath ) , os . ModePerm )
2015-08-06 17:48:11 +03:00
if err = ioutil . WriteFile ( tmpPath , [ ] byte ( key . Content ) , 0644 ) ; err != nil {
2014-02-17 19:57:23 +04:00
return err
}
2015-11-03 02:54:47 +03:00
stdout , stderr , err := process . Exec ( "AddPublicKey" , "ssh-keygen" , "-lf" , tmpPath )
2014-02-17 19:57:23 +04:00
if err != nil {
2015-11-03 02:54:47 +03:00
return errors . New ( "ssh-keygen -lf: " + stderr )
2014-03-16 14:16:03 +04:00
} else if len ( stdout ) < 2 {
2014-10-12 04:34:48 +04:00
return errors . New ( "not enough output for calculating fingerprint: " + stdout )
2014-03-16 14:16:03 +04:00
}
key . Fingerprint = strings . Split ( stdout , " " ) [ 1 ]
// Save SSH key.
2015-08-06 17:48:11 +03:00
if _ , err = e . Insert ( key ) ; err != nil {
2014-03-16 14:16:03 +04:00
return err
2015-08-06 17:48:11 +03:00
}
2015-12-11 13:02:33 +03:00
// Don't need to rewrite this file if builtin SSH server is enabled.
if setting . StartSSHServer {
return nil
}
2015-08-06 17:48:11 +03:00
return saveAuthorizedKeyFile ( key )
}
// AddPublicKey adds new public key to database and authorized_keys file.
2015-12-03 08:24:37 +03:00
func AddPublicKey ( ownerID int64 , name , content string ) ( * PublicKey , error ) {
if err := checkKeyContent ( content ) ; err != nil {
return nil , err
2014-02-17 19:57:23 +04:00
}
2015-08-06 17:48:11 +03:00
// Key name of same user cannot be duplicated.
has , err := x . Where ( "owner_id=? AND name=?" , ownerID , name ) . Get ( new ( PublicKey ) )
if err != nil {
2015-12-03 08:24:37 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
} else if has {
2015-12-03 08:24:37 +03:00
return nil , ErrKeyNameAlreadyUsed { ownerID , name }
2015-08-06 17:48:11 +03:00
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
2015-12-03 08:24:37 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
}
key := & PublicKey {
OwnerID : ownerID ,
Name : name ,
Content : content ,
Mode : ACCESS_MODE_WRITE ,
Type : KEY_TYPE_USER ,
}
if err = addKey ( sess , key ) ; err != nil {
2015-12-03 08:24:37 +03:00
return nil , fmt . Errorf ( "addKey: %v" , err )
2015-08-06 17:48:11 +03:00
}
2015-12-03 08:24:37 +03:00
return key , sess . Commit ( )
2014-02-17 19:57:23 +04:00
}
2015-08-06 17:48:11 +03:00
// GetPublicKeyByID returns public key by given ID.
func GetPublicKeyByID ( keyID int64 ) ( * PublicKey , error ) {
2014-08-10 02:40:10 +04:00
key := new ( PublicKey )
2015-08-06 17:48:11 +03:00
has , err := x . Id ( keyID ) . Get ( key )
2014-08-10 02:40:10 +04:00
if err != nil {
return nil , err
} else if ! has {
2015-08-06 17:48:11 +03:00
return nil , ErrKeyNotExist { keyID }
2014-08-10 02:40:10 +04:00
}
return key , nil
}
2015-11-09 00:59:56 +03:00
// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
// and returns public key found.
func SearchPublicKeyByContent ( content string ) ( * PublicKey , error ) {
key := new ( PublicKey )
has , err := x . Where ( "content like ?" , content + "%" ) . Get ( key )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrKeyNotExist { }
}
return key , nil
}
2014-11-12 14:48:50 +03:00
// ListPublicKeys returns a list of public keys belongs to given user.
func ListPublicKeys ( uid int64 ) ( [ ] * PublicKey , error ) {
2014-07-26 08:24:27 +04:00
keys := make ( [ ] * PublicKey , 0 , 5 )
2015-08-06 17:48:11 +03:00
return keys , x . Where ( "owner_id=?" , uid ) . Find ( & keys )
2014-05-07 20:09:30 +04:00
}
2014-05-07 00:28:52 +04:00
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
2014-03-22 22:27:03 +04:00
func rewriteAuthorizedKeys ( key * PublicKey , p , tmpP string ) error {
2014-03-16 13:24:13 +04:00
fr , err := os . Open ( p )
if err != nil {
return err
}
defer fr . Close ( )
2014-06-24 12:53:42 +04:00
fw , err := os . OpenFile ( tmpP , os . O_CREATE | os . O_WRONLY | os . O_APPEND , 0600 )
2014-03-16 13:24:13 +04:00
if err != nil {
return err
}
defer fw . Close ( )
2014-05-07 00:28:52 +04:00
isFound := false
2015-08-06 17:48:11 +03:00
keyword := fmt . Sprintf ( "key-%d" , key . ID )
2014-05-07 20:09:30 +04:00
buf := bufio . NewReader ( fr )
for {
line , errRead := buf . ReadString ( '\n' )
line = strings . TrimSpace ( line )
if errRead != nil {
if errRead != io . EOF {
return errRead
}
// Reached end of file, if nothing to read then break,
// otherwise handle the last line.
if len ( line ) == 0 {
break
}
2014-03-16 13:24:13 +04:00
}
// Found the line and copy rest of file.
2014-05-07 20:09:30 +04:00
if ! isFound && strings . Contains ( line , keyword ) && strings . Contains ( line , key . Content ) {
2014-05-07 00:28:52 +04:00
isFound = true
2014-03-16 13:48:20 +04:00
continue
2014-03-16 13:24:13 +04:00
}
// Still finding the line, copy the line that currently read.
2014-05-07 20:09:30 +04:00
if _ , err = fw . WriteString ( line + "\n" ) ; err != nil {
2014-03-16 13:24:13 +04:00
return err
}
2014-05-07 00:28:52 +04:00
2014-05-07 20:09:30 +04:00
if errRead == io . EOF {
break
}
}
2014-03-22 22:27:03 +04:00
return nil
}
2014-03-16 13:24:13 +04:00
2014-08-10 02:40:10 +04:00
// UpdatePublicKey updates given public key.
func UpdatePublicKey ( key * PublicKey ) error {
2015-08-06 17:48:11 +03:00
_ , err := x . Id ( key . ID ) . AllCols ( ) . Update ( key )
2014-08-10 02:40:10 +04:00
return err
}
2015-08-24 22:30:39 +03:00
func deletePublicKey ( e * xorm . Session , keyID int64 ) error {
2015-08-13 01:00:43 +03:00
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
2015-08-24 22:30:39 +03:00
key := & PublicKey { ID : keyID }
2015-08-06 17:48:11 +03:00
has , err := e . Get ( key )
2014-03-22 22:27:03 +04:00
if err != nil {
return err
} else if ! has {
2015-08-06 17:48:11 +03:00
return nil
2014-03-22 22:27:03 +04:00
}
2014-05-07 00:28:52 +04:00
2015-08-24 22:30:39 +03:00
if _ , err = e . Id ( key . ID ) . Delete ( new ( PublicKey ) ) ; err != nil {
2014-03-22 22:27:03 +04:00
return err
}
2015-12-11 13:02:33 +03:00
// Don't need to rewrite this file if builtin SSH server is enabled.
if setting . StartSSHServer {
return nil
}
2015-12-20 05:43:32 +03:00
fpath := filepath . Join ( setting . SSHRootPath , "authorized_keys" )
tmpPath := filepath . Join ( setting . SSHRootPath , "authorized_keys.tmp" )
2014-05-07 20:09:30 +04:00
if err = rewriteAuthorizedKeys ( key , fpath , tmpPath ) ; err != nil {
2014-03-22 22:27:03 +04:00
return err
2014-05-07 20:09:30 +04:00
} else if err = os . Remove ( fpath ) ; err != nil {
2014-03-16 13:24:13 +04:00
return err
}
2014-05-07 20:09:30 +04:00
return os . Rename ( tmpPath , fpath )
2014-02-17 19:57:23 +04:00
}
2014-12-31 21:07:51 +03:00
2015-08-06 17:48:11 +03:00
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
2015-12-03 08:24:37 +03:00
func DeletePublicKey ( doer * User , id int64 ) ( err error ) {
key , err := GetPublicKeyByID ( id )
2015-08-20 12:11:29 +03:00
if err != nil {
2015-12-03 08:24:37 +03:00
if IsErrKeyNotExist ( err ) {
return nil
}
return fmt . Errorf ( "GetPublicKeyByID: %v" , err )
}
// Check if user has access to delete this key.
2015-12-06 01:13:13 +03:00
if ! doer . IsAdmin && doer . Id != key . OwnerID {
2015-12-03 08:24:37 +03:00
return ErrKeyAccessDenied { doer . Id , key . ID , "public" }
2015-08-20 12:11:29 +03:00
}
2015-08-06 17:48:11 +03:00
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
2015-08-24 22:30:39 +03:00
if err = deletePublicKey ( sess , id ) ; err != nil {
2015-08-06 17:48:11 +03:00
return err
}
return sess . Commit ( )
}
2015-02-02 01:21:56 +03:00
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
2014-12-31 21:07:51 +03:00
func RewriteAllPublicKeys ( ) error {
2015-02-02 01:21:56 +03:00
sshOpLocker . Lock ( )
defer sshOpLocker . Unlock ( )
2015-12-20 05:43:32 +03:00
tmpPath := filepath . Join ( setting . SSHRootPath , "authorized_keys.tmp" )
2015-03-15 05:37:23 +03:00
f , err := os . OpenFile ( tmpPath , os . O_RDWR | os . O_CREATE | os . O_TRUNC , 0600 )
2014-12-31 21:07:51 +03:00
if err != nil {
return err
}
2015-02-02 01:21:56 +03:00
defer os . Remove ( tmpPath )
2014-12-31 21:07:51 +03:00
2015-02-02 01:21:56 +03:00
err = x . Iterate ( new ( PublicKey ) , func ( idx int , bean interface { } ) ( err error ) {
_ , err = f . WriteString ( ( bean . ( * PublicKey ) ) . GetAuthorizedString ( ) )
return err
} )
f . Close ( )
if err != nil {
return err
2014-12-31 21:07:51 +03:00
}
2015-02-02 01:21:56 +03:00
2015-12-20 05:43:32 +03:00
fpath := filepath . Join ( setting . SSHRootPath , "authorized_keys" )
2015-02-02 01:21:56 +03:00
if com . IsExist ( fpath ) {
if err = os . Remove ( fpath ) ; err != nil {
return err
}
}
if err = os . Rename ( tmpPath , fpath ) ; err != nil {
2014-12-31 21:07:51 +03:00
return err
}
2015-02-02 01:21:56 +03:00
return nil
2014-12-31 21:07:51 +03:00
}
2015-08-06 17:48:11 +03:00
// ________ .__ ____ __.
// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
// \/ \/|__| \/ \/ \/\/
// 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
2015-11-19 05:21:47 +03:00
Content string ` xorm:"-" `
2015-08-06 17:48:11 +03:00
Created time . Time ` xorm:"CREATED" `
Updated time . Time // Note: Updated must below Created for AfterSet.
HasRecentActivity bool ` xorm:"-" `
HasUsed bool ` xorm:"-" `
}
func ( k * DeployKey ) AfterSet ( colName string , _ xorm . Cell ) {
switch colName {
case "created" :
k . HasUsed = k . Updated . After ( k . Created )
k . HasRecentActivity = k . Updated . Add ( 7 * 24 * time . Hour ) . After ( time . Now ( ) )
}
}
2015-11-19 05:21:47 +03:00
// GetContent gets associated public key content.
func ( k * DeployKey ) GetContent ( ) error {
pkey , err := GetPublicKeyByID ( k . KeyID )
if err != nil {
return err
}
k . Content = pkey . Content
return nil
}
2015-08-06 17:48:11 +03:00
func checkDeployKey ( e Engine , keyID , repoID int64 , name string ) error {
// 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.
2015-11-19 05:21:47 +03:00
func addDeployKey ( e * xorm . Session , keyID , repoID int64 , name , fingerprint string ) ( * DeployKey , error ) {
if err := checkDeployKey ( e , keyID , repoID , name ) ; err != nil {
return nil , err
2015-08-06 17:48:11 +03:00
}
2015-11-19 05:21:47 +03:00
key := & DeployKey {
2015-08-06 17:48:11 +03:00
KeyID : keyID ,
RepoID : repoID ,
Name : name ,
Fingerprint : fingerprint ,
2015-11-19 05:21:47 +03:00
}
_ , err := e . Insert ( key )
return key , err
2015-08-06 17:48:11 +03:00
}
// HasDeployKey returns true if public key is a deploy key of given repository.
func HasDeployKey ( keyID , repoID int64 ) bool {
has , _ := x . Where ( "key_id=? AND repo_id=?" , keyID , repoID ) . Get ( new ( DeployKey ) )
return has
}
// AddDeployKey add new deploy key to database and authorized_keys file.
2015-11-19 05:21:47 +03:00
func AddDeployKey ( repoID int64 , name , content string ) ( * DeployKey , error ) {
if err := checkKeyContent ( content ) ; err != nil {
return nil , err
2015-08-06 17:48:11 +03:00
}
2015-11-19 05:21:47 +03:00
pkey := & PublicKey {
2015-08-06 17:48:11 +03:00
Content : content ,
Mode : ACCESS_MODE_READ ,
Type : KEY_TYPE_DEPLOY ,
}
2015-11-19 05:21:47 +03:00
has , err := x . Get ( pkey )
2015-08-06 17:48:11 +03:00
if err != nil {
2015-11-19 05:21:47 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
2015-11-19 05:21:47 +03:00
return nil , err
2015-08-06 17:48:11 +03:00
}
// First time use this deploy key.
if ! has {
2015-11-19 05:21:47 +03:00
if err = addKey ( sess , pkey ) ; err != nil {
return nil , fmt . Errorf ( "addKey: %v" , err )
2015-08-06 17:48:11 +03:00
}
}
2015-11-19 05:21:47 +03:00
key , err := addDeployKey ( sess , pkey . ID , repoID , name , pkey . Fingerprint )
if err != nil {
return nil , fmt . Errorf ( "addDeployKey: %v" , err )
2015-08-06 17:48:11 +03:00
}
2015-11-19 05:21:47 +03:00
return key , sess . Commit ( )
}
// GetDeployKeyByID returns deploy key by given ID.
func GetDeployKeyByID ( id int64 ) ( * DeployKey , error ) {
key := new ( DeployKey )
has , err := x . Id ( id ) . Get ( key )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrDeployKeyNotExist { id , 0 , 0 }
}
return key , nil
2015-08-06 17:48:11 +03:00
}
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
func GetDeployKeyByRepo ( keyID , repoID int64 ) ( * DeployKey , error ) {
key := & DeployKey {
KeyID : keyID ,
RepoID : repoID ,
}
2015-11-19 05:21:47 +03:00
has , err := x . Get ( key )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrDeployKeyNotExist { 0 , keyID , repoID }
}
return key , nil
2015-08-06 17:48:11 +03:00
}
// UpdateDeployKey updates deploy key information.
func UpdateDeployKey ( key * DeployKey ) error {
_ , err := x . Id ( key . ID ) . AllCols ( ) . Update ( key )
return err
}
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
2015-12-03 08:24:37 +03:00
func DeleteDeployKey ( doer * User , id int64 ) error {
key , err := GetDeployKeyByID ( id )
2015-08-06 17:48:11 +03:00
if err != nil {
2015-12-03 08:24:37 +03:00
if IsErrDeployKeyNotExist ( err ) {
return nil
}
return fmt . Errorf ( "GetDeployKeyByID: %v" , err )
}
// Check if user has access to delete this key.
2015-12-06 01:13:13 +03:00
if ! doer . IsAdmin {
repo , err := GetRepositoryByID ( key . RepoID )
if err != nil {
return fmt . Errorf ( "GetRepositoryByID: %v" , err )
}
yes , err := HasAccess ( doer , repo , ACCESS_MODE_ADMIN )
if err != nil {
return fmt . Errorf ( "HasAccess: %v" , err )
} else if ! yes {
return ErrKeyAccessDenied { doer . Id , key . ID , "deploy" }
}
2015-08-06 17:48:11 +03:00
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
2015-08-24 22:30:39 +03:00
if _ , err = sess . Id ( key . ID ) . Delete ( new ( DeployKey ) ) ; err != nil {
2015-08-06 17:48:11 +03:00
return fmt . Errorf ( "delete deploy key[%d]: %v" , key . ID , err )
}
// Check if this is the last reference to same key content.
2015-12-03 08:24:37 +03:00
has , err := sess . Where ( "key_id=?" , key . KeyID ) . Get ( new ( DeployKey ) )
2015-08-06 17:48:11 +03:00
if err != nil {
return err
} else if ! has {
2015-08-24 22:30:39 +03:00
if err = deletePublicKey ( sess , key . KeyID ) ; err != nil {
2015-08-06 17:48:11 +03:00
return err
}
}
return sess . Commit ( )
}
// ListDeployKeys returns all deploy keys by given repository ID.
func ListDeployKeys ( repoID int64 ) ( [ ] * DeployKey , error ) {
keys := make ( [ ] * DeployKey , 0 , 5 )
return keys , x . Where ( "repo_id=?" , repoID ) . Find ( & keys )
}