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.

ssh_key.go 22 kB

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