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 23 kB

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