From 2d76de2574d2e3734ed8e566845042e6fb281a6e Mon Sep 17 00:00:00 2001
From: Unknwon <u@gogs.io>
Date: Tue, 26 Jul 2016 17:26:48 +0800
Subject: [PATCH] #3281 fix x.Iterate returns nothing inside session scope with
 SQLite3

---
 README.md          |   2 +-
 gogs.go            |   2 +-
 models/models.go   |   3 +-
 models/ssh_key.go  | 101 +++++++--------------------------------------
 models/user.go     |  23 +++++++----
 templates/.VERSION |   2 +-
 6 files changed, 35 insertions(+), 98 deletions(-)

diff --git a/README.md b/README.md
index 7bd28e2363..6b77c18d9f 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
 
 ![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
 
-##### Current tip version: 0.9.56 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
+##### Current tip version: 0.9.57 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
 
 | Web | UI  | Preview  |
 |:-------------:|:-------:|:-------:|
diff --git a/gogs.go b/gogs.go
index cefb82fdb4..534e6cd3fa 100644
--- a/gogs.go
+++ b/gogs.go
@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.9.56.0726"
+const APP_VER = "0.9.57.0726"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())
diff --git a/models/models.go b/models/models.go
index 758e1e8270..6a3abc54b3 100644
--- a/models/models.go
+++ b/models/models.go
@@ -27,9 +27,10 @@ type Engine interface {
 	Exec(string, ...interface{}) (sql.Result, error)
 	Find(interface{}, ...interface{}) error
 	Get(interface{}) (bool, error)
+	Id(interface{}) *xorm.Session
 	Insert(...interface{}) (int64, error)
 	InsertOne(interface{}) (int64, error)
-	Id(interface{}) *xorm.Session
+	Iterate(interface{}, xorm.IterFunc) error
 	Sql(string, ...interface{}) *xorm.Session
 	Where(string, ...interface{}) *xorm.Session
 }
diff --git a/models/ssh_key.go b/models/ssh_key.go
index 4fbc2e8d47..0925b9ba76 100644
--- a/models/ssh_key.go
+++ b/models/ssh_key.go
@@ -5,12 +5,10 @@
 package models
 
 import (
-	"bufio"
 	"encoding/base64"
 	"encoding/binary"
 	"errors"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"math/big"
 	"os"
@@ -24,6 +22,7 @@ import (
 	"github.com/go-xorm/xorm"
 	"golang.org/x/crypto/ssh"
 
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/log"
 	"github.com/gogits/gogs/modules/process"
 	"github.com/gogits/gogs/modules/setting"
@@ -457,96 +456,20 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) {
 	return keys, x.Where("owner_id = ?", uid).Find(&keys)
 }
 
-// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
-func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
-	fr, err := os.Open(p)
-	if err != nil {
-		return err
-	}
-	defer fr.Close()
-
-	fw, err := os.OpenFile(tmpP, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
-	if err != nil {
-		return err
-	}
-	defer fw.Close()
-
-	isFound := false
-	keyword := fmt.Sprintf("key-%d", key.ID)
-	buf := bufio.NewReader(fr)
-	for {
-		line, errRead := buf.ReadString('\n')
-		line = strings.TrimSpace(line)
-
-		if errRead != nil {
-			if errRead != io.EOF {
-				return errRead
-			}
-
-			// Reached end of file, if nothing to read then break,
-			// otherwise handle the last line.
-			if len(line) == 0 {
-				break
-			}
-		}
-
-		// Found the line and copy rest of file.
-		if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) {
-			isFound = true
-			continue
-		}
-		// Still finding the line, copy the line that currently read.
-		if _, err = fw.WriteString(line + "\n"); err != nil {
-			return err
-		}
-
-		if errRead == io.EOF {
-			break
-		}
-	}
-
-	if !isFound {
-		log.Warn("SSH key %d not found in authorized_keys file for deletion", key.ID)
-	}
-
-	return nil
-}
-
 // UpdatePublicKey updates given public key.
 func UpdatePublicKey(key *PublicKey) error {
 	_, err := x.Id(key.ID).AllCols().Update(key)
 	return err
 }
 
-func deletePublicKey(e *xorm.Session, keyID int64) error {
-	sshOpLocker.Lock()
-	defer sshOpLocker.Unlock()
-
-	key := &PublicKey{ID: keyID}
-	has, err := e.Get(key)
-	if err != nil {
-		return err
-	} else if !has {
+// deletePublicKeys does the actual key deletion but does not update authorized_keys file.
+func deletePublicKeys(e *xorm.Session, keyIDs ...int64) error {
+	if len(keyIDs) == 0 {
 		return nil
 	}
 
-	if _, err = e.Id(key.ID).Delete(new(PublicKey)); err != nil {
-		return err
-	}
-
-	// Don't need to rewrite this file if builtin SSH server is enabled.
-	if setting.SSH.StartBuiltinServer {
-		return nil
-	}
-
-	fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
-	tmpPath := fpath + ".tmp"
-	if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
-		return err
-	} else if err = os.Remove(fpath); err != nil {
-		return err
-	}
-	return os.Rename(tmpPath, fpath)
+	_, err := e.In("id", strings.Join(base.Int64sToStrings(keyIDs), ",")).Delete(new(PublicKey))
+	return err
 }
 
 // DeletePublicKey deletes SSH key information both in database and authorized_keys file.
@@ -570,14 +493,20 @@ func DeletePublicKey(doer *User, id int64) (err error) {
 		return err
 	}
 
-	if err = deletePublicKey(sess, id); err != nil {
+	if err = deletePublicKeys(sess, id); err != nil {
 		return err
 	}
 
-	return sess.Commit()
+	if err = sess.Commit(); err != nil {
+		return err
+	}
+
+	return RewriteAllPublicKeys()
 }
 
 // RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
+// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
+// outsite any session scope independently.
 func RewriteAllPublicKeys() error {
 	sshOpLocker.Lock()
 	defer sshOpLocker.Unlock()
@@ -814,7 +743,7 @@ func DeleteDeployKey(doer *User, id int64) error {
 	if err != nil {
 		return err
 	} else if !has {
-		if err = deletePublicKey(sess, key.KeyID); err != nil {
+		if err = deletePublicKeys(sess, key.KeyID); err != nil {
 			return err
 		}
 	}
diff --git a/models/user.go b/models/user.go
index a22162dc2d..5e9b60addb 100644
--- a/models/user.go
+++ b/models/user.go
@@ -768,10 +768,13 @@ func deleteUser(e *xorm.Session, u *User) error {
 	if err = e.Find(&keys, &PublicKey{OwnerID: u.ID}); err != nil {
 		return fmt.Errorf("get all public keys: %v", err)
 	}
-	for _, key := range keys {
-		if err = deletePublicKey(e, key.ID); err != nil {
-			return fmt.Errorf("deletePublicKey: %v", err)
-		}
+
+	keyIDs := make([]int64, len(keys))
+	for i := range keys {
+		keyIDs[i] = keys[i].ID
+	}
+	if err = deletePublicKeys(e, keyIDs...); err != nil {
+		return fmt.Errorf("deletePublicKeys: %v", err)
 	}
 	// ***** END: PublicKey *****
 
@@ -788,7 +791,6 @@ func deleteUser(e *xorm.Session, u *User) error {
 	// Note: There are something just cannot be roll back,
 	//	so just keep error logs of those operations.
 
-	RewriteAllPublicKeys()
 	os.RemoveAll(UserPath(u.Name))
 	os.Remove(u.CustomAvatarPath())
 
@@ -809,15 +811,20 @@ func DeleteUser(u *User) (err error) {
 		return err
 	}
 
-	return sess.Commit()
+	if err = sess.Commit(); err != nil {
+		return err
+	}
+
+	return RewriteAllPublicKeys()
 }
 
 // DeleteInactivateUsers deletes all inactivate users and email addresses.
 func DeleteInactivateUsers() (err error) {
 	users := make([]*User, 0, 10)
-	if err = x.Where("is_active=?", false).Find(&users); err != nil {
+	if err = x.Where("is_active = ?", false).Find(&users); err != nil {
 		return fmt.Errorf("get all inactive users: %v", err)
 	}
+	// FIXME: should only update authorized_keys file once after all deletions.
 	for _, u := range users {
 		if err = DeleteUser(u); err != nil {
 			// Ignore users that were set inactive by admin.
@@ -828,7 +835,7 @@ func DeleteInactivateUsers() (err error) {
 		}
 	}
 
-	_, err = x.Where("is_activated=?", false).Delete(new(EmailAddress))
+	_, err = x.Where("is_activated = ?", false).Delete(new(EmailAddress))
 	return err
 }
 
diff --git a/templates/.VERSION b/templates/.VERSION
index 65ea7222ae..03ddfb19c9 100644
--- a/templates/.VERSION
+++ b/templates/.VERSION
@@ -1 +1 @@
-0.9.56.0726
\ No newline at end of file
+0.9.57.0726
\ No newline at end of file