You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

publickey.go 18 kB

11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
10 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. // Copyright 2014 The Gogs 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. package models
  5. import (
  6. "bufio"
  7. "encoding/base64"
  8. "encoding/binary"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "io/ioutil"
  13. "os"
  14. "path"
  15. "path/filepath"
  16. "strings"
  17. "sync"
  18. "time"
  19. "github.com/Unknwon/com"
  20. "github.com/go-xorm/xorm"
  21. "github.com/gogits/gogs/modules/log"
  22. "github.com/gogits/gogs/modules/process"
  23. "github.com/gogits/gogs/modules/setting"
  24. )
  25. const (
  26. // "### autogenerated by gitgos, DO NOT EDIT\n"
  27. _TPL_PUBLICK_KEY = `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
  28. )
  29. var sshOpLocker = sync.Mutex{}
  30. var SSHPath string // SSH directory.
  31. // homeDir returns the home directory of current user.
  32. func homeDir() string {
  33. home, err := com.HomeDir()
  34. if err != nil {
  35. log.Fatal(4, "Fail to get home directory: %v", err)
  36. }
  37. return home
  38. }
  39. func init() {
  40. // Determine and create .ssh path.
  41. SSHPath = filepath.Join(homeDir(), ".ssh")
  42. if err := os.MkdirAll(SSHPath, 0700); err != nil {
  43. log.Fatal(4, "fail to create '%s': %v", SSHPath, err)
  44. }
  45. }
  46. type KeyType int
  47. const (
  48. KEY_TYPE_USER = iota + 1
  49. KEY_TYPE_DEPLOY
  50. )
  51. // PublicKey represents a SSH or deploy key.
  52. type PublicKey struct {
  53. ID int64 `xorm:"pk autoincr"`
  54. OwnerID int64 `xorm:"INDEX NOT NULL"`
  55. Name string `xorm:"NOT NULL"`
  56. Fingerprint string `xorm:"NOT NULL"`
  57. Content string `xorm:"TEXT NOT NULL"`
  58. Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
  59. Type KeyType `xorm:"NOT NULL DEFAULT 1"`
  60. Created time.Time `xorm:"CREATED"`
  61. Updated time.Time // Note: Updated must below Created for AfterSet.
  62. HasRecentActivity bool `xorm:"-"`
  63. HasUsed bool `xorm:"-"`
  64. }
  65. func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) {
  66. switch colName {
  67. case "created":
  68. k.HasUsed = k.Updated.After(k.Created)
  69. k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
  70. }
  71. }
  72. // OmitEmail returns content of public key but without e-mail address.
  73. func (k *PublicKey) OmitEmail() string {
  74. return strings.Join(strings.Split(k.Content, " ")[:2], " ")
  75. }
  76. // GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
  77. func (key *PublicKey) GetAuthorizedString() string {
  78. return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, key.ID, setting.CustomConf, key.Content)
  79. }
  80. func extractTypeFromBase64Key(key string) (string, error) {
  81. b, err := base64.StdEncoding.DecodeString(key)
  82. if err != nil || len(b) < 4 {
  83. return "", errors.New("Invalid key format")
  84. }
  85. keyLength := int(binary.BigEndian.Uint32(b))
  86. if len(b) < 4+keyLength {
  87. return "", errors.New("Invalid key format")
  88. }
  89. return string(b[4 : 4+keyLength]), nil
  90. }
  91. // parseKeyString parses any key string in openssh or ssh2 format to clean openssh string (rfc4253)
  92. func parseKeyString(content string) (string, error) {
  93. // Transform all legal line endings to a single "\n"
  94. s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1)
  95. lines := strings.Split(s, "\n")
  96. var keyType, keyContent, keyComment string
  97. if len(lines) == 1 {
  98. // Parse openssh format
  99. parts := strings.SplitN(lines[0], " ", 3)
  100. switch len(parts) {
  101. case 0:
  102. return "", errors.New("Empty key")
  103. case 1:
  104. keyContent = parts[0]
  105. case 2:
  106. keyType = parts[0]
  107. keyContent = parts[1]
  108. default:
  109. keyType = parts[0]
  110. keyContent = parts[1]
  111. keyComment = parts[2]
  112. }
  113. // If keyType is not given, extract it from content. If given, validate it
  114. if len(keyType) == 0 {
  115. if t, err := extractTypeFromBase64Key(keyContent); err == nil {
  116. keyType = t
  117. } else {
  118. return "", err
  119. }
  120. } else {
  121. if t, err := extractTypeFromBase64Key(keyContent); err != nil || keyType != t {
  122. return "", err
  123. }
  124. }
  125. } else {
  126. // Parse SSH2 file format.
  127. continuationLine := false
  128. for _, line := range lines {
  129. // Skip lines that:
  130. // 1) are a continuation of the previous line,
  131. // 2) contain ":" as that are comment lines
  132. // 3) contain "-" as that are begin and end tags
  133. if continuationLine || strings.ContainsAny(line, ":-") {
  134. continuationLine = strings.HasSuffix(line, "\\")
  135. } else {
  136. keyContent = keyContent + line
  137. }
  138. }
  139. if t, err := extractTypeFromBase64Key(keyContent); err == nil {
  140. keyType = t
  141. } else {
  142. return "", err
  143. }
  144. }
  145. return keyType + " " + keyContent + " " + keyComment, nil
  146. }
  147. // CheckPublicKeyString checks if the given public key string is recognized by SSH.
  148. func CheckPublicKeyString(content string) (_ string, err error) {
  149. content, err = parseKeyString(content)
  150. if err != nil {
  151. return "", err
  152. }
  153. content = strings.TrimRight(content, "\n\r")
  154. if strings.ContainsAny(content, "\n\r") {
  155. return "", errors.New("only a single line with a single key please")
  156. }
  157. // write the key to a file…
  158. tmpFile, err := ioutil.TempFile(os.TempDir(), "keytest")
  159. if err != nil {
  160. return "", err
  161. }
  162. tmpPath := tmpFile.Name()
  163. defer os.Remove(tmpPath)
  164. tmpFile.WriteString(content)
  165. tmpFile.Close()
  166. // Check if ssh-keygen recognizes its contents.
  167. stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-lf", tmpPath)
  168. if err != nil {
  169. return "", errors.New("ssh-keygen -lf: " + stderr)
  170. } else if len(stdout) < 2 {
  171. return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
  172. }
  173. // The ssh-keygen in Windows does not print key type, so no need go further.
  174. if setting.IsWindows {
  175. return content, nil
  176. }
  177. sshKeygenOutput := strings.Split(stdout, " ")
  178. if len(sshKeygenOutput) < 4 {
  179. return content, ErrKeyUnableVerify{stdout}
  180. }
  181. // Check if key type and key size match.
  182. if !setting.Service.DisableMinimumKeySizeCheck {
  183. keySize := com.StrTo(sshKeygenOutput[0]).MustInt()
  184. if keySize == 0 {
  185. return "", errors.New("cannot get key size of the given key")
  186. }
  187. keyType := strings.Trim(sshKeygenOutput[len(sshKeygenOutput)-1], " ()\n")
  188. if minimumKeySize := setting.Service.MinimumKeySizes[keyType]; minimumKeySize == 0 {
  189. return "", fmt.Errorf("unrecognized public key type: %s", keyType)
  190. } else if keySize < minimumKeySize {
  191. return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
  192. }
  193. }
  194. return content, nil
  195. }
  196. // saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
  197. func saveAuthorizedKeyFile(keys ...*PublicKey) error {
  198. sshOpLocker.Lock()
  199. defer sshOpLocker.Unlock()
  200. fpath := filepath.Join(SSHPath, "authorized_keys")
  201. f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  202. if err != nil {
  203. return err
  204. }
  205. defer f.Close()
  206. fi, err := f.Stat()
  207. if err != nil {
  208. return err
  209. }
  210. // FIXME: following command does not support in Windows.
  211. if !setting.IsWindows {
  212. // .ssh directory should have mode 700, and authorized_keys file should have mode 600.
  213. if fi.Mode().Perm() > 0600 {
  214. log.Error(4, "authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
  215. if err = f.Chmod(0600); err != nil {
  216. return err
  217. }
  218. }
  219. }
  220. for _, key := range keys {
  221. if _, err = f.WriteString(key.GetAuthorizedString()); err != nil {
  222. return err
  223. }
  224. }
  225. return nil
  226. }
  227. // checkKeyContent onlys checks if key content has been used as public key,
  228. // it is OK to use same key as deploy key for multiple repositories/users.
  229. func checkKeyContent(content string) error {
  230. has, err := x.Get(&PublicKey{
  231. Content: content,
  232. Type: KEY_TYPE_USER,
  233. })
  234. if err != nil {
  235. return err
  236. } else if has {
  237. return ErrKeyAlreadyExist{0, content}
  238. }
  239. return nil
  240. }
  241. func addKey(e Engine, key *PublicKey) (err error) {
  242. // Calculate fingerprint.
  243. tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
  244. "id_rsa.pub"), "\\", "/", -1)
  245. os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
  246. if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
  247. return err
  248. }
  249. stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
  250. if err != nil {
  251. return errors.New("ssh-keygen -lf: " + stderr)
  252. } else if len(stdout) < 2 {
  253. return errors.New("not enough output for calculating fingerprint: " + stdout)
  254. }
  255. key.Fingerprint = strings.Split(stdout, " ")[1]
  256. // Save SSH key.
  257. if _, err = e.Insert(key); err != nil {
  258. return err
  259. }
  260. return saveAuthorizedKeyFile(key)
  261. }
  262. // AddPublicKey adds new public key to database and authorized_keys file.
  263. func AddPublicKey(ownerID int64, name, content string) (err error) {
  264. if err = checkKeyContent(content); err != nil {
  265. return err
  266. }
  267. // Key name of same user cannot be duplicated.
  268. has, err := x.Where("owner_id=? AND name=?", ownerID, name).Get(new(PublicKey))
  269. if err != nil {
  270. return err
  271. } else if has {
  272. return ErrKeyNameAlreadyUsed{ownerID, name}
  273. }
  274. sess := x.NewSession()
  275. defer sessionRelease(sess)
  276. if err = sess.Begin(); err != nil {
  277. return err
  278. }
  279. key := &PublicKey{
  280. OwnerID: ownerID,
  281. Name: name,
  282. Content: content,
  283. Mode: ACCESS_MODE_WRITE,
  284. Type: KEY_TYPE_USER,
  285. }
  286. if err = addKey(sess, key); err != nil {
  287. return fmt.Errorf("addKey: %v", err)
  288. }
  289. return sess.Commit()
  290. }
  291. // GetPublicKeyByID returns public key by given ID.
  292. func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
  293. key := new(PublicKey)
  294. has, err := x.Id(keyID).Get(key)
  295. if err != nil {
  296. return nil, err
  297. } else if !has {
  298. return nil, ErrKeyNotExist{keyID}
  299. }
  300. return key, nil
  301. }
  302. // SearchPublicKeyByContent searches content as prefix (leak e-mail part)
  303. // and returns public key found.
  304. func SearchPublicKeyByContent(content string) (*PublicKey, error) {
  305. key := new(PublicKey)
  306. has, err := x.Where("content like ?", content+"%").Get(key)
  307. if err != nil {
  308. return nil, err
  309. } else if !has {
  310. return nil, ErrKeyNotExist{}
  311. }
  312. return key, nil
  313. }
  314. // ListPublicKeys returns a list of public keys belongs to given user.
  315. func ListPublicKeys(uid int64) ([]*PublicKey, error) {
  316. keys := make([]*PublicKey, 0, 5)
  317. return keys, x.Where("owner_id=?", uid).Find(&keys)
  318. }
  319. // rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
  320. func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
  321. fr, err := os.Open(p)
  322. if err != nil {
  323. return err
  324. }
  325. defer fr.Close()
  326. fw, err := os.OpenFile(tmpP, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
  327. if err != nil {
  328. return err
  329. }
  330. defer fw.Close()
  331. isFound := false
  332. keyword := fmt.Sprintf("key-%d", key.ID)
  333. buf := bufio.NewReader(fr)
  334. for {
  335. line, errRead := buf.ReadString('\n')
  336. line = strings.TrimSpace(line)
  337. if errRead != nil {
  338. if errRead != io.EOF {
  339. return errRead
  340. }
  341. // Reached end of file, if nothing to read then break,
  342. // otherwise handle the last line.
  343. if len(line) == 0 {
  344. break
  345. }
  346. }
  347. // Found the line and copy rest of file.
  348. if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) {
  349. isFound = true
  350. continue
  351. }
  352. // Still finding the line, copy the line that currently read.
  353. if _, err = fw.WriteString(line + "\n"); err != nil {
  354. return err
  355. }
  356. if errRead == io.EOF {
  357. break
  358. }
  359. }
  360. return nil
  361. }
  362. // UpdatePublicKey updates given public key.
  363. func UpdatePublicKey(key *PublicKey) error {
  364. _, err := x.Id(key.ID).AllCols().Update(key)
  365. return err
  366. }
  367. func deletePublicKey(e *xorm.Session, keyID int64) error {
  368. sshOpLocker.Lock()
  369. defer sshOpLocker.Unlock()
  370. key := &PublicKey{ID: keyID}
  371. has, err := e.Get(key)
  372. if err != nil {
  373. return err
  374. } else if !has {
  375. return nil
  376. }
  377. if _, err = e.Id(key.ID).Delete(new(PublicKey)); err != nil {
  378. return err
  379. }
  380. fpath := filepath.Join(SSHPath, "authorized_keys")
  381. tmpPath := filepath.Join(SSHPath, "authorized_keys.tmp")
  382. if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
  383. return err
  384. } else if err = os.Remove(fpath); err != nil {
  385. return err
  386. }
  387. return os.Rename(tmpPath, fpath)
  388. }
  389. // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
  390. func DeletePublicKey(id int64) (err error) {
  391. has, err := x.Id(id).Get(new(PublicKey))
  392. if err != nil {
  393. return err
  394. } else if !has {
  395. return nil
  396. }
  397. sess := x.NewSession()
  398. defer sessionRelease(sess)
  399. if err = sess.Begin(); err != nil {
  400. return err
  401. }
  402. if err = deletePublicKey(sess, id); err != nil {
  403. return err
  404. }
  405. return sess.Commit()
  406. }
  407. // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
  408. func RewriteAllPublicKeys() error {
  409. sshOpLocker.Lock()
  410. defer sshOpLocker.Unlock()
  411. tmpPath := filepath.Join(SSHPath, "authorized_keys.tmp")
  412. f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  413. if err != nil {
  414. return err
  415. }
  416. defer os.Remove(tmpPath)
  417. err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
  418. _, err = f.WriteString((bean.(*PublicKey)).GetAuthorizedString())
  419. return err
  420. })
  421. f.Close()
  422. if err != nil {
  423. return err
  424. }
  425. fpath := filepath.Join(SSHPath, "authorized_keys")
  426. if com.IsExist(fpath) {
  427. if err = os.Remove(fpath); err != nil {
  428. return err
  429. }
  430. }
  431. if err = os.Rename(tmpPath, fpath); err != nil {
  432. return err
  433. }
  434. return nil
  435. }
  436. // ________ .__ ____ __.
  437. // \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
  438. // | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
  439. // | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
  440. // /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
  441. // \/ \/|__| \/ \/ \/\/
  442. // DeployKey represents deploy key information and its relation with repository.
  443. type DeployKey struct {
  444. ID int64 `xorm:"pk autoincr"`
  445. KeyID int64 `xorm:"UNIQUE(s) INDEX"`
  446. RepoID int64 `xorm:"UNIQUE(s) INDEX"`
  447. Name string
  448. Fingerprint string
  449. Content string `xorm:"-"`
  450. Created time.Time `xorm:"CREATED"`
  451. Updated time.Time // Note: Updated must below Created for AfterSet.
  452. HasRecentActivity bool `xorm:"-"`
  453. HasUsed bool `xorm:"-"`
  454. }
  455. func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
  456. switch colName {
  457. case "created":
  458. k.HasUsed = k.Updated.After(k.Created)
  459. k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now())
  460. }
  461. }
  462. // GetContent gets associated public key content.
  463. func (k *DeployKey) GetContent() error {
  464. pkey, err := GetPublicKeyByID(k.KeyID)
  465. if err != nil {
  466. return err
  467. }
  468. k.Content = pkey.Content
  469. return nil
  470. }
  471. func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
  472. // Note: We want error detail, not just true or false here.
  473. has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
  474. if err != nil {
  475. return err
  476. } else if has {
  477. return ErrDeployKeyAlreadyExist{keyID, repoID}
  478. }
  479. has, err = e.Where("repo_id=? AND name=?", repoID, name).Get(new(DeployKey))
  480. if err != nil {
  481. return err
  482. } else if has {
  483. return ErrDeployKeyNameAlreadyUsed{repoID, name}
  484. }
  485. return nil
  486. }
  487. // addDeployKey adds new key-repo relation.
  488. func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) {
  489. if err := checkDeployKey(e, keyID, repoID, name); err != nil {
  490. return nil, err
  491. }
  492. key := &DeployKey{
  493. KeyID: keyID,
  494. RepoID: repoID,
  495. Name: name,
  496. Fingerprint: fingerprint,
  497. }
  498. _, err := e.Insert(key)
  499. return key, err
  500. }
  501. // HasDeployKey returns true if public key is a deploy key of given repository.
  502. func HasDeployKey(keyID, repoID int64) bool {
  503. has, _ := x.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
  504. return has
  505. }
  506. // AddDeployKey add new deploy key to database and authorized_keys file.
  507. func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
  508. if err := checkKeyContent(content); err != nil {
  509. return nil, err
  510. }
  511. pkey := &PublicKey{
  512. Content: content,
  513. Mode: ACCESS_MODE_READ,
  514. Type: KEY_TYPE_DEPLOY,
  515. }
  516. has, err := x.Get(pkey)
  517. if err != nil {
  518. return nil, err
  519. }
  520. sess := x.NewSession()
  521. defer sessionRelease(sess)
  522. if err = sess.Begin(); err != nil {
  523. return nil, err
  524. }
  525. // First time use this deploy key.
  526. if !has {
  527. if err = addKey(sess, pkey); err != nil {
  528. return nil, fmt.Errorf("addKey: %v", err)
  529. }
  530. }
  531. key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint)
  532. if err != nil {
  533. return nil, fmt.Errorf("addDeployKey: %v", err)
  534. }
  535. return key, sess.Commit()
  536. }
  537. // GetDeployKeyByID returns deploy key by given ID.
  538. func GetDeployKeyByID(id int64) (*DeployKey, error) {
  539. key := new(DeployKey)
  540. has, err := x.Id(id).Get(key)
  541. if err != nil {
  542. return nil, err
  543. } else if !has {
  544. return nil, ErrDeployKeyNotExist{id, 0, 0}
  545. }
  546. return key, nil
  547. }
  548. // GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
  549. func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
  550. key := &DeployKey{
  551. KeyID: keyID,
  552. RepoID: repoID,
  553. }
  554. has, err := x.Get(key)
  555. if err != nil {
  556. return nil, err
  557. } else if !has {
  558. return nil, ErrDeployKeyNotExist{0, keyID, repoID}
  559. }
  560. return key, nil
  561. }
  562. // UpdateDeployKey updates deploy key information.
  563. func UpdateDeployKey(key *DeployKey) error {
  564. _, err := x.Id(key.ID).AllCols().Update(key)
  565. return err
  566. }
  567. // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
  568. func DeleteDeployKey(id int64) error {
  569. key := &DeployKey{ID: id}
  570. has, err := x.Id(key.ID).Get(key)
  571. if err != nil {
  572. return err
  573. } else if !has {
  574. return nil
  575. }
  576. sess := x.NewSession()
  577. defer sessionRelease(sess)
  578. if err = sess.Begin(); err != nil {
  579. return err
  580. }
  581. if _, err = sess.Id(key.ID).Delete(new(DeployKey)); err != nil {
  582. return fmt.Errorf("delete deploy key[%d]: %v", key.ID, err)
  583. }
  584. // Check if this is the last reference to same key content.
  585. has, err = sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
  586. if err != nil {
  587. return err
  588. } else if !has {
  589. if err = deletePublicKey(sess, key.KeyID); err != nil {
  590. return err
  591. }
  592. }
  593. return sess.Commit()
  594. }
  595. // ListDeployKeys returns all deploy keys by given repository ID.
  596. func ListDeployKeys(repoID int64) ([]*DeployKey, error) {
  597. keys := make([]*DeployKey, 0, 5)
  598. return keys, x.Where("repo_id=?", repoID).Find(&keys)
  599. }