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

707 lines
18 KiB

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