From 3367a7a5f9ac7aa7285d4267284698ee83c42b57 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Wed, 30 May 2018 19:49:32 +0200 Subject: [PATCH 01/16] Rewrite Upgrade Tool * Reformat code * Add download for sqlite-src-* * Add extract for sqlite source * Add auto merge of UserAuth module into Amalgamation --- .gitignore | 7 ++ tool/upgrade.go | 111 ----------------------- upgrade/upgrade.go | 218 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 111 deletions(-) delete mode 100644 tool/upgrade.go create mode 100644 upgrade/upgrade.go diff --git a/.gitignore b/.gitignore index c219728..fa0e6b5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,10 @@ # VSCode .vscode + +# Exclude from upgrade +upgrade/*.c +upgrade/*.h + +# Exclude upgrade binary +upgrade/upgrade diff --git a/tool/upgrade.go b/tool/upgrade.go deleted file mode 100644 index 275f915..0000000 --- a/tool/upgrade.go +++ /dev/null @@ -1,111 +0,0 @@ -// +build ignore - -package main - -import ( - "archive/zip" - "bufio" - "bytes" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "os" - "path" - "path/filepath" - "strings" - - "github.com/PuerkitoBio/goquery" -) - -func main() { - site := "https://www.sqlite.org/download.html" - fmt.Printf("scraping %v\n", site) - doc, err := goquery.NewDocument(site) - if err != nil { - log.Fatal(err) - } - var url string - doc.Find("a").Each(func(_ int, s *goquery.Selection) { - if url == "" && strings.HasPrefix(s.Text(), "sqlite-amalgamation-") { - url = "https://www.sqlite.org/2018/" + s.Text() - } - }) - if url == "" { - return - } - fmt.Printf("downloading %v\n", url) - resp, err := http.Get(url) - if err != nil { - log.Fatal(err) - } - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - resp.Body.Close() - log.Fatal(err) - } - - fmt.Printf("extracting %v\n", path.Base(url)) - r, err := zip.NewReader(bytes.NewReader(b), resp.ContentLength) - if err != nil { - resp.Body.Close() - log.Fatal(err) - } - resp.Body.Close() - - for _, zf := range r.File { - var f *os.File - switch path.Base(zf.Name) { - case "sqlite3.c": - f, err = os.Create("sqlite3-binding.c") - case "sqlite3.h": - f, err = os.Create("sqlite3-binding.h") - case "sqlite3ext.h": - f, err = os.Create("sqlite3ext.h") - default: - continue - } - if err != nil { - log.Fatal(err) - } - zr, err := zf.Open() - if err != nil { - log.Fatal(err) - } - - _, err = io.WriteString(f, "#ifndef USE_LIBSQLITE3\n") - if err != nil { - zr.Close() - f.Close() - log.Fatal(err) - } - scanner := bufio.NewScanner(zr) - for scanner.Scan() { - text := scanner.Text() - if text == `#include "sqlite3.h"` { - text = `#include "sqlite3-binding.h"` - } - _, err = fmt.Fprintln(f, text) - if err != nil { - break - } - } - err = scanner.Err() - if err != nil { - zr.Close() - f.Close() - log.Fatal(err) - } - _, err = io.WriteString(f, "#else // USE_LIBSQLITE3\n // If users really want to link against the system sqlite3 we\n// need to make this file a noop.\n #endif") - if err != nil { - zr.Close() - f.Close() - log.Fatal(err) - } - zr.Close() - f.Close() - fmt.Printf("extracted %v\n", filepath.Base(f.Name())) - } -} diff --git a/upgrade/upgrade.go b/upgrade/upgrade.go new file mode 100644 index 0000000..d800227 --- /dev/null +++ b/upgrade/upgrade.go @@ -0,0 +1,218 @@ +// +build !cgo +// +build upgrade + +package main + +import ( + "archive/zip" + "bufio" + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/PuerkitoBio/goquery" +) + +func download(prefix string) (url string, content []byte, err error) { + year := time.Now().Year() + + site := "https://www.sqlite.org/download.html" + //fmt.Printf("scraping %v\n", site) + doc, err := goquery.NewDocument(site) + if err != nil { + log.Fatal(err) + } + + doc.Find("a").Each(func(_ int, s *goquery.Selection) { + if strings.HasPrefix(s.Text(), prefix) { + url = fmt.Sprintf("https://www.sqlite.org/%d/", year) + s.Text() + } + }) + + if url == "" { + return "", nil, fmt.Errorf("Unable to find prefix '%s' on sqlite.org", prefix) + } + + fmt.Printf("Downloading %v\n", url) + resp, err := http.Get(url) + if err != nil { + log.Fatal(err) + } + + // Ready Body Content + content, err = ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return "", nil, err + } + + return url, content, nil +} + +func mergeFile(src string, dst string) error { + defer func() error { + fmt.Printf("Removing: %s\n", src) + err := os.Remove(src) + + if err != nil { + return err + } + + return nil + }() + + // Open destination + fdst, err := os.OpenFile(dst, os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + return err + } + defer fdst.Close() + + // Read source content + content, err := ioutil.ReadFile(src) + if err != nil { + return err + } + + // Add Additional newline + if _, err := fdst.WriteString("\n"); err != nil { + return err + } + + fmt.Printf("Merging: %s into %s\n", src, dst) + if _, err = fdst.Write(content); err != nil { + return err + } + + return nil +} + +func main() { + fmt.Println("Go-SQLite3 Upgrade Tool") + + // Download Amalgamation + _, amalgamation, err := download("sqlite-amalgamation-") + if err != nil { + fmt.Println("Failed to download: sqlite-amalgamation; %s", err) + } + + // Download Source + _, source, err := download("sqlite-src-") + if err != nil { + fmt.Println("Failed to download: sqlite-src; %s", err) + } + + // Create Amalgamation Zip Reader + rAmalgamation, err := zip.NewReader(bytes.NewReader(amalgamation), int64(len(amalgamation))) + if err != nil { + log.Fatal(err) + } + + // Create Source Zip Reader + rSource, err := zip.NewReader(bytes.NewReader(source), int64(len(source))) + if err != nil { + log.Fatal(err) + } + + // Extract Amalgamation + for _, zf := range rAmalgamation.File { + var f *os.File + switch path.Base(zf.Name) { + case "sqlite3.c": + f, err = os.Create("sqlite3-binding.c") + case "sqlite3.h": + f, err = os.Create("sqlite3-binding.h") + case "sqlite3ext.h": + f, err = os.Create("sqlite3ext.h") + default: + continue + } + if err != nil { + log.Fatal(err) + } + zr, err := zf.Open() + if err != nil { + log.Fatal(err) + } + + _, err = io.WriteString(f, "#ifndef USE_LIBSQLITE3\n") + if err != nil { + zr.Close() + f.Close() + log.Fatal(err) + } + scanner := bufio.NewScanner(zr) + for scanner.Scan() { + text := scanner.Text() + if text == `#include "sqlite3.h"` { + text = `#include "sqlite3-binding.h"` + } + _, err = fmt.Fprintln(f, text) + if err != nil { + break + } + } + err = scanner.Err() + if err != nil { + zr.Close() + f.Close() + log.Fatal(err) + } + _, err = io.WriteString(f, "#else // USE_LIBSQLITE3\n // If users really want to link against the system sqlite3 we\n// need to make this file a noop.\n #endif") + if err != nil { + zr.Close() + f.Close() + log.Fatal(err) + } + zr.Close() + f.Close() + fmt.Printf("Extracted: %v\n", filepath.Base(f.Name())) + } + + //Extract Source + for _, zf := range rSource.File { + var f *os.File + switch path.Base(zf.Name) { + case "userauth.c": + f, err = os.Create("userauth.c") + case "sqlite3userauth.h": + f, err = os.Create("userauth.h") + default: + continue + } + if err != nil { + log.Fatal(err) + } + zr, err := zf.Open() + if err != nil { + log.Fatal(err) + } + + _, err = io.Copy(f, zr) + if err != nil { + log.Fatal(err) + } + + zr.Close() + f.Close() + fmt.Printf("extracted %v\n", filepath.Base(f.Name())) + } + + // Merge SQLite User Authentication into amalgamation + if err := mergeFile("userauth.c", "sqlite3-binding.c"); err != nil { + log.Fatal(err) + } + if err := mergeFile("userauth.h", "sqlite3-binding.h"); err != nil { + log.Fatal(err) + } + + os.Exit(0) +} From 6ae7f98274c6e254f84a9b8349b4a4eed0bee8a5 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Wed, 30 May 2018 22:36:49 +0200 Subject: [PATCH 02/16] ADD: User authentication * User Authentication Implementation * Rename file to conform to fileformat `sqlite3_*_omit.go` * Updated sqlite3-binding.* with new upgrade tool * Add: callbackRetNil required for error type return because of adding `RegisterFunc`s directly on the connection. * Add: TestCreateAuthDatabase --- callback.go | 10 + sqlite3-binding.c | 357 +++++++++++++++++- sqlite3-binding.h | 98 ++++- sqlite3.go | 129 ++++++- ...nsion.go => sqlite3_load_extension_omit.go | 0 sqlite3_opt_userauth.go | 127 +++++++ sqlite3_opt_userauth_omit.go | 65 ++++ sqlite3_opt_userauth_test.go | 39 ++ 8 files changed, 821 insertions(+), 4 deletions(-) rename sqlite3_omit_load_extension.go => sqlite3_load_extension_omit.go (100%) create mode 100644 sqlite3_opt_userauth.go create mode 100644 sqlite3_opt_userauth_omit.go create mode 100644 sqlite3_opt_userauth_test.go diff --git a/callback.go b/callback.go index 29ece3d..5a735c0 100644 --- a/callback.go +++ b/callback.go @@ -331,8 +331,18 @@ func callbackRetText(ctx *C.sqlite3_context, v reflect.Value) error { return nil } +func callbackRetNil(ctx *C.sqlite3_context, v reflect.Value) error { + return nil +} + func callbackRet(typ reflect.Type) (callbackRetConverter, error) { switch typ.Kind() { + case reflect.Interface: + errorInterface := reflect.TypeOf((*error)(nil)).Elem() + if typ.Implements(errorInterface) { + return callbackRetNil, nil + } + fallthrough case reflect.Slice: if typ.Elem().Kind() != reflect.Uint8 { return nil, errors.New("the only supported slice type is []byte") diff --git a/sqlite3-binding.c b/sqlite3-binding.c index f3d13e7..cf030cf 100644 --- a/sqlite3-binding.c +++ b/sqlite3-binding.c @@ -209943,4 +209943,359 @@ SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } #else // USE_LIBSQLITE3 // If users really want to link against the system sqlite3 we // need to make this file a noop. - #endif \ No newline at end of file + #endif +/* +** 2014-09-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the bulk of the implementation of the +** user-authentication extension feature. Some parts of the user- +** authentication code are contained within the SQLite core (in the +** src/ subdirectory of the main source code tree) but those parts +** that could reasonable be separated out are moved into this file. +** +** To compile with the user-authentication feature, append this file to +** end of an SQLite amalgamation, then add the SQLITE_USER_AUTHENTICATION +** compile-time option. See the user-auth.txt file in the same source +** directory as this file for additional information. +*/ +#ifdef SQLITE_USER_AUTHENTICATION +#ifndef SQLITEINT_H +# include "sqliteInt.h" +#endif + +/* +** Prepare an SQL statement for use by the user authentication logic. +** Return a pointer to the prepared statement on success. Return a +** NULL pointer if there is an error of any kind. +*/ +static sqlite3_stmt *sqlite3UserAuthPrepare( + sqlite3 *db, + const char *zFormat, + ... +){ + sqlite3_stmt *pStmt; + char *zSql; + int rc; + va_list ap; + int savedFlags = db->flags; + + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + if( zSql==0 ) return 0; + db->flags |= SQLITE_WriteSchema; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + db->flags = savedFlags; + sqlite3_free(zSql); + if( rc ){ + sqlite3_finalize(pStmt); + pStmt = 0; + } + return pStmt; +} + +/* +** Check to see if the sqlite_user table exists in database zDb. +*/ +static int userTableExists(sqlite3 *db, const char *zDb){ + int rc; + sqlite3_mutex_enter(db->mutex); + sqlite3BtreeEnterAll(db); + if( db->init.busy==0 ){ + char *zErr = 0; + sqlite3Init(db, &zErr); + sqlite3DbFree(db, zErr); + } + rc = sqlite3FindTable(db, "sqlite_user", zDb)!=0; + sqlite3BtreeLeaveAll(db); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Check to see if database zDb has a "sqlite_user" table and if it does +** whether that table can authenticate zUser with nPw,zPw. Write one of +** the UAUTH_* user authorization level codes into *peAuth and return a +** result code. +*/ +static int userAuthCheckLogin( + sqlite3 *db, /* The database connection to check */ + const char *zDb, /* Name of specific database to check */ + u8 *peAuth /* OUT: One of UAUTH_* constants */ +){ + sqlite3_stmt *pStmt; + int rc; + + *peAuth = UAUTH_Unknown; + if( !userTableExists(db, "main") ){ + *peAuth = UAUTH_Admin; /* No sqlite_user table. Everybody is admin. */ + return SQLITE_OK; + } + if( db->auth.zAuthUser==0 ){ + *peAuth = UAUTH_Fail; + return SQLITE_OK; + } + pStmt = sqlite3UserAuthPrepare(db, + "SELECT pw=sqlite_crypt(?1,pw), isAdmin FROM \"%w\".sqlite_user" + " WHERE uname=?2", zDb); + if( pStmt==0 ) return SQLITE_NOMEM; + sqlite3_bind_blob(pStmt, 1, db->auth.zAuthPW, db->auth.nAuthPW,SQLITE_STATIC); + sqlite3_bind_text(pStmt, 2, db->auth.zAuthUser, -1, SQLITE_STATIC); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW && sqlite3_column_int(pStmt,0) ){ + *peAuth = sqlite3_column_int(pStmt, 1) + UAUTH_User; + }else{ + *peAuth = UAUTH_Fail; + } + return sqlite3_finalize(pStmt); +} +int sqlite3UserAuthCheckLogin( + sqlite3 *db, /* The database connection to check */ + const char *zDb, /* Name of specific database to check */ + u8 *peAuth /* OUT: One of UAUTH_* constants */ +){ + int rc; + u8 savedAuthLevel; + assert( zDb!=0 ); + assert( peAuth!=0 ); + savedAuthLevel = db->auth.authLevel; + db->auth.authLevel = UAUTH_Admin; + rc = userAuthCheckLogin(db, zDb, peAuth); + db->auth.authLevel = savedAuthLevel; + return rc; +} + +/* +** If the current authLevel is UAUTH_Unknown, the take actions to figure +** out what authLevel should be +*/ +void sqlite3UserAuthInit(sqlite3 *db){ + if( db->auth.authLevel==UAUTH_Unknown ){ + u8 authLevel = UAUTH_Fail; + sqlite3UserAuthCheckLogin(db, "main", &authLevel); + db->auth.authLevel = authLevel; + if( authLevelflags &= ~SQLITE_WriteSchema; + } +} + +/* +** Implementation of the sqlite_crypt(X,Y) function. +** +** If Y is NULL then generate a new hash for password X and return that +** hash. If Y is not null, then generate a hash for password X using the +** same salt as the previous hash Y and return the new hash. +*/ +void sqlite3CryptFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + const char *zIn; + int nIn, ii; + u8 *zOut; + char zSalt[8]; + zIn = sqlite3_value_blob(argv[0]); + nIn = sqlite3_value_bytes(argv[0]); + if( sqlite3_value_type(argv[1])==SQLITE_BLOB + && sqlite3_value_bytes(argv[1])==nIn+sizeof(zSalt) + ){ + memcpy(zSalt, sqlite3_value_blob(argv[1]), sizeof(zSalt)); + }else{ + sqlite3_randomness(sizeof(zSalt), zSalt); + } + zOut = sqlite3_malloc( nIn+sizeof(zSalt) ); + if( zOut==0 ){ + sqlite3_result_error_nomem(context); + }else{ + memcpy(zOut, zSalt, sizeof(zSalt)); + for(ii=0; iiauth.authLevel = UAUTH_Unknown; + sqlite3_free(db->auth.zAuthUser); + sqlite3_free(db->auth.zAuthPW); + memset(&db->auth, 0, sizeof(db->auth)); + db->auth.zAuthUser = sqlite3_mprintf("%s", zUsername); + if( db->auth.zAuthUser==0 ) return SQLITE_NOMEM; + db->auth.zAuthPW = sqlite3_malloc( nPW+1 ); + if( db->auth.zAuthPW==0 ) return SQLITE_NOMEM; + memcpy(db->auth.zAuthPW,zPW,nPW); + db->auth.nAuthPW = nPW; + rc = sqlite3UserAuthCheckLogin(db, "main", &authLevel); + db->auth.authLevel = authLevel; + sqlite3ExpirePreparedStatements(db); + if( rc ){ + return rc; /* OOM error, I/O error, etc. */ + } + if( authLevelauth.authLevelauth.zAuthUser==0 ){ + assert( isAdmin!=0 ); + sqlite3_user_authenticate(db, zUsername, aPW, nPW); + } + return SQLITE_OK; +} + +/* +** The sqlite3_user_change() interface can be used to change a users +** login credentials or admin privilege. Any user can change their own +** login credentials. Only an admin user can change another users login +** credentials or admin privilege setting. No user may change their own +** admin privilege setting. +*/ +int sqlite3_user_change( + sqlite3 *db, /* Database connection */ + const char *zUsername, /* Username to change */ + const char *aPW, /* Modified password or credentials */ + int nPW, /* Number of bytes in aPW[] */ + int isAdmin /* Modified admin privilege for the user */ +){ + sqlite3_stmt *pStmt; + int rc; + u8 authLevel; + + authLevel = db->auth.authLevel; + if( authLevelauth.zAuthUser, zUsername)!=0 ){ + if( db->auth.authLevelauth.authLevel = UAUTH_Admin; + if( !userTableExists(db, "main") ){ + /* This routine is a no-op if the user to be modified does not exist */ + }else{ + pStmt = sqlite3UserAuthPrepare(db, + "UPDATE sqlite_user SET isAdmin=%d, pw=sqlite_crypt(?1,NULL)" + " WHERE uname=%Q", isAdmin, zUsername); + if( pStmt==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_bind_blob(pStmt, 1, aPW, nPW, SQLITE_STATIC); + sqlite3_step(pStmt); + rc = sqlite3_finalize(pStmt); + } + } + db->auth.authLevel = authLevel; + return rc; +} + +/* +** The sqlite3_user_delete() interface can be used (by an admin user only) +** to delete a user. The currently logged-in user cannot be deleted, +** which guarantees that there is always an admin user and hence that +** the database cannot be converted into a no-authentication-required +** database. +*/ +int sqlite3_user_delete( + sqlite3 *db, /* Database connection */ + const char *zUsername /* Username to remove */ +){ + sqlite3_stmt *pStmt; + if( db->auth.authLevelauth.zAuthUser, zUsername)==0 ){ + /* Cannot delete self */ + return SQLITE_AUTH; + } + if( !userTableExists(db, "main") ){ + /* This routine is a no-op if the user to be deleted does not exist */ + return SQLITE_OK; + } + pStmt = sqlite3UserAuthPrepare(db, + "DELETE FROM sqlite_user WHERE uname=%Q", zUsername); + if( pStmt==0 ) return SQLITE_NOMEM; + sqlite3_step(pStmt); + return sqlite3_finalize(pStmt); +} + +#endif /* SQLITE_USER_AUTHENTICATION */ diff --git a/sqlite3-binding.h b/sqlite3-binding.h index 4da4101..702a254 100644 --- a/sqlite3-binding.h +++ b/sqlite3-binding.h @@ -11179,4 +11179,100 @@ struct fts5_api { #else // USE_LIBSQLITE3 // If users really want to link against the system sqlite3 we // need to make this file a noop. - #endif \ No newline at end of file + #endif +/* +** 2014-09-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the application interface definitions for the +** user-authentication extension feature. +** +** To compile with the user-authentication feature, append this file to +** end of an SQLite amalgamation header file ("sqlite3.h"), then add +** the SQLITE_USER_AUTHENTICATION compile-time option. See the +** user-auth.txt file in the same source directory as this file for +** additional information. +*/ +#ifdef SQLITE_USER_AUTHENTICATION + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** If a database contains the SQLITE_USER table, then the +** sqlite3_user_authenticate() interface must be invoked with an +** appropriate username and password prior to enable read and write +** access to the database. +** +** Return SQLITE_OK on success or SQLITE_ERROR if the username/password +** combination is incorrect or unknown. +** +** If the SQLITE_USER table is not present in the database file, then +** this interface is a harmless no-op returnning SQLITE_OK. +*/ +int sqlite3_user_authenticate( + sqlite3 *db, /* The database connection */ + const char *zUsername, /* Username */ + const char *aPW, /* Password or credentials */ + int nPW /* Number of bytes in aPW[] */ +); + +/* +** The sqlite3_user_add() interface can be used (by an admin user only) +** to create a new user. When called on a no-authentication-required +** database, this routine converts the database into an authentication- +** required database, automatically makes the added user an +** administrator, and logs in the current connection as that user. +** The sqlite3_user_add() interface only works for the "main" database, not +** for any ATTACH-ed databases. Any call to sqlite3_user_add() by a +** non-admin user results in an error. +*/ +int sqlite3_user_add( + sqlite3 *db, /* Database connection */ + const char *zUsername, /* Username to be added */ + const char *aPW, /* Password or credentials */ + int nPW, /* Number of bytes in aPW[] */ + int isAdmin /* True to give new user admin privilege */ +); + +/* +** The sqlite3_user_change() interface can be used to change a users +** login credentials or admin privilege. Any user can change their own +** login credentials. Only an admin user can change another users login +** credentials or admin privilege setting. No user may change their own +** admin privilege setting. +*/ +int sqlite3_user_change( + sqlite3 *db, /* Database connection */ + const char *zUsername, /* Username to change */ + const char *aPW, /* New password or credentials */ + int nPW, /* Number of bytes in aPW[] */ + int isAdmin /* Modified admin privilege for the user */ +); + +/* +** The sqlite3_user_delete() interface can be used (by an admin user only) +** to delete a user. The currently logged-in user cannot be deleted, +** which guarantees that there is always an admin user and hence that +** the database cannot be converted into a no-authentication-required +** database. +*/ +int sqlite3_user_delete( + sqlite3 *db, /* Database connection */ + const char *zUsername /* Username to remove */ +); + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif + +#endif /* SQLITE_USER_AUTHENTICATION */ diff --git a/sqlite3.go b/sqlite3.go index 75a5ee8..8a15696 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -891,6 +891,9 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // Options var loc *time.Location + authCreate := false + authUser := "" + authPass := "" mutex := C.int(C.SQLITE_OPEN_FULLMUTEX) txlock := "BEGIN" @@ -916,6 +919,17 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil, err } + // Authentication + if _, ok := params["_auth"]; ok { + authCreate = true + } + if val := params.Get("_auth_user"); val != "" { + authUser = val + } + if val := params.Get("_auth_pass"); val != "" { + authPass = val + } + // _loc if val := params.Get("_loc"); val != "" { switch strings.ToLower(val) { @@ -1248,7 +1262,93 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil } + // USER AUTHENTICATION + // + // User Authentication is always performed even when + // sqlite_userauth is not compiled in, because without user authentication + // the authentication is a no-op. + // + // Workflow + // - Authenticate + // ON::SUCCESS => Continue + // ON::SQLITE_AUTH => Return error and exit Open(...) + // + // - Activate User Authentication + // Check if the user wants to activate User Authentication. + // If so then first create a temporary AuthConn to the database + // This is possible because we are already succesfully authenticated. + // + // - Check if `sqlite_user`` table exists + // YES => Add the provided user from DSN as Admin User and + // activate user authentication. + // NO => Continue + // + + // Create connection to SQLite + conn := &SQLiteConn{db: db, loc: loc, txlock: txlock} + + // Preform Authentication + if err := conn.Authenticate(authUser, authPass); err != nil { + return nil, err + } + + // Register Authentication Functions into connection + // + // Register Authentication function + // Authenticate will perform an authentication of the provided username + // and password against the database. + // + // If a database contains the SQLITE_USER table, then the + // call to Authenticate must be invoked with an + // appropriate username and password prior to enable read and write + //access to the database. + // + // Return SQLITE_OK on success or SQLITE_ERROR if the username/password + // combination is incorrect or unknown. + // + // If the SQLITE_USER table is not present in the database file, then + // this interface is a harmless no-op returnning SQLITE_OK. + if err := conn.RegisterFunc("authenticate", conn.Authenticate, true); err != nil { + return nil, err + } + // + // Register AuthUserAdd + // AuthUserAdd can be used (by an admin user only) + // to create a new user. When called on a no-authentication-required + // database, this routine converts the database into an authentication- + // required database, automatically makes the added user an + // administrator, and logs in the current connection as that user. + // The AuthUserAdd only works for the "main" database, not + // for any ATTACH-ed databases. Any call to AuthUserAdd by a + // non-admin user results in an error. + if err := conn.RegisterFunc("auth_user_add", conn.AuthUserAdd, true); err != nil { + return nil, err + } + // + // AuthUserChange can be used to change a users + // login credentials or admin privilege. Any user can change their own + // login credentials. Only an admin user can change another users login + // credentials or admin privilege setting. No user may change their own + // admin privilege setting. + if err := conn.RegisterFunc("auth_user_change", conn.AuthUserChange, true); err != nil { + return nil, err + } + // + // AuthUserDelete can be used (by an admin user only) + // to delete a user. The currently logged-in user cannot be deleted, + // which guarantees that there is always an admin user and hence that + // the database cannot be converted into a no-authentication-required + // database. + if err := conn.RegisterFunc("auth_user_delete", conn.AuthUserDelete, true); err != nil { + return nil, err + } + // Auto Vacuum + // Moved auto_vacuum command, the user preference for auto_vacuum needs to be implemented directly after + // the authentication and before the sqlite_user table gets created if the user + // decides to activate User Authentication because + // auto_vacuum needs to be set before any tables are created + // and activating user authentication creates the internal table `sqlite_user`. if autoVacuum > -1 { if err := exec(fmt.Sprintf("PRAGMA auto_vacuum = %d;", autoVacuum)); err != nil { C.sqlite3_close_v2(db) @@ -1256,6 +1356,33 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } } + // Check if user wants to activate User Authentication + if authCreate { + // Before going any further, we need to check that the user + // has provided an username and password within the DSN. + // We are not allowed to continue. + if len(authUser) < 0 { + return nil, fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'") + } + if len(authPass) < 0 { + return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'") + } + + // TODO: Table exists check for table 'sqlite_user' + // replace 'authExists := false' with return value of table exists check + // + // REPLACE BY RESULT FROM TABLE EXISTS + // SELECT count(type) as exists FROM sqlite_master WHERE type='table' AND name='sqlite_user'; + // Scan result 'exists' and use it instead of boolean below. + authExists := false + + if !authExists { + if err := conn.AuthUserAdd(authUser, authPass, true); err != nil { + return nil, err + } + } + } + // Case Sensitive LIKE if caseSensitiveLike > -1 { if err := exec(fmt.Sprintf("PRAGMA case_sensitive_like = %d;", caseSensitiveLike)); err != nil { @@ -1347,8 +1474,6 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } } - conn := &SQLiteConn{db: db, loc: loc, txlock: txlock} - if len(d.Extensions) > 0 { if err := conn.loadExtensions(d.Extensions); err != nil { conn.Close() diff --git a/sqlite3_omit_load_extension.go b/sqlite3_load_extension_omit.go similarity index 100% rename from sqlite3_omit_load_extension.go rename to sqlite3_load_extension_omit.go diff --git a/sqlite3_opt_userauth.go b/sqlite3_opt_userauth.go new file mode 100644 index 0000000..3572f87 --- /dev/null +++ b/sqlite3_opt_userauth.go @@ -0,0 +1,127 @@ +// Copyright (C) 2018 G.J.R. Timmer . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build sqlite_userauth + +package sqlite3 + +/* +#cgo CFLAGS: -DSQLITE_USER_AUTHENTICATION +#cgo LDFLAGS: -lm +#ifndef USE_LIBSQLITE3 +#include +#else +#include +#endif +#include + +static int +_sqlite3_user_authenticate(sqlite3* db, const char* zUsername, const char* aPW, int nPW) +{ + return sqlite3_user_authenticate(db, zUsername, aPW, nPW); +} + +static int +_sqlite3_user_add(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin) +{ + return sqlite3_user_add(db, zUsername, aPW, nPW, isAdmin); +} + +static int +_sqlite3_user_change(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin) +{ + return sqlite3_user_change(db, zUsername, aPW, nPW, isAdmin); +} + +static int +_sqlite3_user_delete(sqlite3* db, const char* zUsername) +{ + return sqlite3_user_delete(db, zUsername); +} +*/ +import "C" + +const ( + SQLITE_AUTH = C.SQLITE_AUTH +) + +// Authenticate will perform an authentication of the provided username +// and password against the database. +// +// If a database contains the SQLITE_USER table, then the +// call to Authenticate must be invoked with an +// appropriate username and password prior to enable read and write +//access to the database. +// +// Return SQLITE_OK on success or SQLITE_ERROR if the username/password +// combination is incorrect or unknown. +// +// If the SQLITE_USER table is not present in the database file, then +// this interface is a harmless no-op returnning SQLITE_OK. +func (c *SQLiteConn) Authenticate(username, password string) error { + rv := C._sqlite3_user_authenticate(c.db, C.CString(username), C.CString(password), C.int(len(password))) + if rv != C.SQLITE_OK { + return c.lastError() + } + + return nil +} + +// AuthUserAdd can be used (by an admin user only) +// to create a new user. When called on a no-authentication-required +// database, this routine converts the database into an authentication- +// required database, automatically makes the added user an +// administrator, and logs in the current connection as that user. +// The AuthUserAdd only works for the "main" database, not +// for any ATTACH-ed databases. Any call to AuthUserAdd by a +// non-admin user results in an error. +func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { + isAdmin := 0 + if admin { + isAdmin = 1 + } + + rv := C._sqlite3_user_add(c.db, C.CString(username), C.CString(password), C.int(len(password)), C.int(isAdmin)) + if rv != C.SQLITE_OK { + return c.lastError() + } + + return nil +} + +// AuthUserChange can be used to change a users +// login credentials or admin privilege. Any user can change their own +// login credentials. Only an admin user can change another users login +// credentials or admin privilege setting. No user may change their own +// admin privilege setting. +func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error { + isAdmin := 0 + if admin { + isAdmin = 1 + } + + rv := C._sqlite3_user_change(c.db, C.CString(username), C.CString(password), C.int(len(password)), C.int(isAdmin)) + if rv != C.SQLITE_OK { + return c.lastError() + } + + return nil +} + +// AuthUserDelete can be used (by an admin user only) +// to delete a user. The currently logged-in user cannot be deleted, +// which guarantees that there is always an admin user and hence that +// the database cannot be converted into a no-authentication-required +// database. +func (c *SQLiteConn) AuthUserDelete(username string) error { + rv := C._sqlite3_user_delete(c.db, C.CString(username)) + if rv != C.SQLITE_OK { + return c.lastError() + } + + return nil +} + +// EOF diff --git a/sqlite3_opt_userauth_omit.go b/sqlite3_opt_userauth_omit.go new file mode 100644 index 0000000..0ae92da --- /dev/null +++ b/sqlite3_opt_userauth_omit.go @@ -0,0 +1,65 @@ +// Copyright (C) 2018 G.J.R. Timmer . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build !sqlite_userauth + +package sqlite3 + +import ( + "C" +) + +// Authenticate will perform an authentication of the provided username +// and password against the database. +// +// If a database contains the SQLITE_USER table, then the +// call to Authenticate must be invoked with an +// appropriate username and password prior to enable read and write +//access to the database. +// +// Return SQLITE_OK on success or SQLITE_ERROR if the username/password +// combination is incorrect or unknown. +// +// If the SQLITE_USER table is not present in the database file, then +// this interface is a harmless no-op returnning SQLITE_OK. +func (c *SQLiteConn) Authenticate(username, password string) error { + // NOOP + return nil +} + +// AuthUserAdd can be used (by an admin user only) +// to create a new user. When called on a no-authentication-required +// database, this routine converts the database into an authentication- +// required database, automatically makes the added user an +// administrator, and logs in the current connection as that user. +// The AuthUserAdd only works for the "main" database, not +// for any ATTACH-ed databases. Any call to AuthUserAdd by a +// non-admin user results in an error. +func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { + // NOOP + return nil +} + +// AuthUserChange can be used to change a users +// login credentials or admin privilege. Any user can change their own +// login credentials. Only an admin user can change another users login +// credentials or admin privilege setting. No user may change their own +// admin privilege setting. +func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error { + // NOOP + return nil +} + +// AuthUserDelete can be used (by an admin user only) +// to delete a user. The currently logged-in user cannot be deleted, +// which guarantees that there is always an admin user and hence that +// the database cannot be converted into a no-authentication-required +// database. +func (c *SQLiteConn) AuthUserDelete(username string) error { + // NOOP + return nil +} + +// EOF diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go new file mode 100644 index 0000000..e1bf538 --- /dev/null +++ b/sqlite3_opt_userauth_test.go @@ -0,0 +1,39 @@ +// Copyright (C) 2018 G.J.R. Timmer . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build sqlite_userauth + +package sqlite3 + +import ( + "database/sql" + "fmt" + "testing" +) + +func TestCreateAuthDatabase(t *testing.T) { + tempFilename := TempFilename(t) + fmt.Println(tempFilename) // debug + //defer os.Remove(tempFilename) // Disable for debug + + db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + var i int64 + err = db.QueryRow("SELECT count(type) FROM sqlite_master WHERE type='table' AND name='sqlite_user';").Scan(&i) + if err != nil { + t.Fatal(err) + } + t.Logf("sqlite_user exists: %d", i) + + _, err = db.Exec("SELECT auth_user_add('test', 'test', false);", nil) + if err != nil { + t.Fatal(err) + } + +} From 2d9b52a48268af5ae75da998c2cf4a4bfd195e83 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Thu, 31 May 2018 09:46:38 +0200 Subject: [PATCH 03/16] Fix: Free memory --- sqlite3_opt_userauth.go | 49 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/sqlite3_opt_userauth.go b/sqlite3_opt_userauth.go index 3572f87..2892dd0 100644 --- a/sqlite3_opt_userauth.go +++ b/sqlite3_opt_userauth.go @@ -42,6 +42,9 @@ _sqlite3_user_delete(sqlite3* db, const char* zUsername) } */ import "C" +import ( + "unsafe" +) const ( SQLITE_AUTH = C.SQLITE_AUTH @@ -61,7 +64,17 @@ const ( // If the SQLITE_USER table is not present in the database file, then // this interface is a harmless no-op returnning SQLITE_OK. func (c *SQLiteConn) Authenticate(username, password string) error { - rv := C._sqlite3_user_authenticate(c.db, C.CString(username), C.CString(password), C.int(len(password))) + // Allocate C Variables + cuser := C.CString(username) + cpass := C.CString(password) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + C.free(unsafe.Pointer(cpass)) + }() + + rv := C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password))) if rv != C.SQLITE_OK { return c.lastError() } @@ -83,7 +96,17 @@ func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { isAdmin = 1 } - rv := C._sqlite3_user_add(c.db, C.CString(username), C.CString(password), C.int(len(password)), C.int(isAdmin)) + // Allocate C Variables + cuser := C.CString(username) + cpass := C.CString(password) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + C.free(unsafe.Pointer(cpass)) + }() + + rv := C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(isAdmin)) if rv != C.SQLITE_OK { return c.lastError() } @@ -102,7 +125,17 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error isAdmin = 1 } - rv := C._sqlite3_user_change(c.db, C.CString(username), C.CString(password), C.int(len(password)), C.int(isAdmin)) + // Allocate C Variables + cuser := C.CString(username) + cpass := C.CString(password) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + C.free(unsafe.Pointer(cpass)) + }() + + rv := C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(isAdmin)) if rv != C.SQLITE_OK { return c.lastError() } @@ -116,7 +149,15 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error // the database cannot be converted into a no-authentication-required // database. func (c *SQLiteConn) AuthUserDelete(username string) error { - rv := C._sqlite3_user_delete(c.db, C.CString(username)) + // Allocate C Variables + cuser := C.CString(username) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + }() + + rv := C._sqlite3_user_delete(c.db, cuser) if rv != C.SQLITE_OK { return c.lastError() } From 183e7d61d12d767363bf91073584300a2d470507 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Thu, 31 May 2018 14:55:22 +0200 Subject: [PATCH 04/16] UPD: User Authentication Implemented table check; only activate User Authentication on a database which has no UA enabled. Closes #582 --- sqlite3.go | 10 ++-------- sqlite3_opt_userauth.go | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index 8a15696..56cb262 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -1368,14 +1368,8 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'") } - // TODO: Table exists check for table 'sqlite_user' - // replace 'authExists := false' with return value of table exists check - // - // REPLACE BY RESULT FROM TABLE EXISTS - // SELECT count(type) as exists FROM sqlite_master WHERE type='table' AND name='sqlite_user'; - // Scan result 'exists' and use it instead of boolean below. - authExists := false - + // Check if User Authentication is Enabled + authExists := conn.AuthIsEnabled() if !authExists { if err := conn.AuthUserAdd(authUser, authPass, true); err != nil { return nil, err diff --git a/sqlite3_opt_userauth.go b/sqlite3_opt_userauth.go index 2892dd0..2f5da0e 100644 --- a/sqlite3_opt_userauth.go +++ b/sqlite3_opt_userauth.go @@ -40,6 +40,23 @@ _sqlite3_user_delete(sqlite3* db, const char* zUsername) { return sqlite3_user_delete(db, zUsername); } + +static int +_sqlite3_auth_is_enabled(sqlite3* db) +{ + int exists = -1; + + sqlite3_stmt *stmt; + sqlite3_prepare_v2(db, "select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';", -1, &stmt, NULL); + + while ( sqlite3_step(stmt) == SQLITE_ROW) { + exists = sqlite3_column_int(stmt, 0); + } + + sqlite3_finalize(stmt); + + return exists; +} */ import "C" import ( @@ -165,4 +182,14 @@ func (c *SQLiteConn) AuthUserDelete(username string) error { return nil } +// Check is database is protected by user authentication +func (c *SQLiteConn) AuthIsEnabled() (exists bool) { + rv := C._sqlite3_auth_is_enabled(c.db) + if rv == 1 { + exists = true + } + + return +} + // EOF From f7f80191021bc250050e7aabae2cff85e893adae Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Thu, 31 May 2018 14:57:32 +0200 Subject: [PATCH 05/16] UPD: TestAuthCreateDatabase * Renamed test * Implemented 'exists' test * WIP #580 --- sqlite3_opt_userauth_test.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index e1bf538..649d10e 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -9,14 +9,13 @@ package sqlite3 import ( "database/sql" - "fmt" + "os" "testing" ) -func TestCreateAuthDatabase(t *testing.T) { +func TestAuthCreateDatabase(t *testing.T) { tempFilename := TempFilename(t) - fmt.Println(tempFilename) // debug - //defer os.Remove(tempFilename) // Disable for debug + defer os.Remove(tempFilename) db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") if err != nil { @@ -24,16 +23,18 @@ func TestCreateAuthDatabase(t *testing.T) { } defer db.Close() - var i int64 - err = db.QueryRow("SELECT count(type) FROM sqlite_master WHERE type='table' AND name='sqlite_user';").Scan(&i) - if err != nil { + // Ping database + if err := db.Ping(); err != nil { t.Fatal(err) } - t.Logf("sqlite_user exists: %d", i) - _, err = db.Exec("SELECT auth_user_add('test', 'test', false);", nil) + var exists bool + err = db.QueryRow("select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';").Scan(&exists) if err != nil { t.Fatal(err) } + if !exists { + t.Fatal("failed to enable User Authentication") + } } From 4a33fcc1d2d467144ec063576131d346efb1f345 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Thu, 31 May 2018 16:42:03 +0200 Subject: [PATCH 06/16] Stash [ci skip] --- sqlite3.go | 8 +++--- sqlite3_opt_userauth.go | 34 +++++++++++++++++++---- sqlite3_opt_userauth_omit.go | 5 ++++ sqlite3_opt_userauth_test.go | 54 ++++++++++++++++++++++++++++++++---- 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index 56cb262..79b96ab 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -1308,7 +1308,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // // If the SQLITE_USER table is not present in the database file, then // this interface is a harmless no-op returnning SQLITE_OK. - if err := conn.RegisterFunc("authenticate", conn.Authenticate, true); err != nil { + if err := conn.RegisterFunc("authenticate", conn.Authenticate, false); err != nil { return nil, err } // @@ -1321,7 +1321,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // The AuthUserAdd only works for the "main" database, not // for any ATTACH-ed databases. Any call to AuthUserAdd by a // non-admin user results in an error. - if err := conn.RegisterFunc("auth_user_add", conn.AuthUserAdd, true); err != nil { + if err := conn.RegisterFunc("auth_user_add", conn.AuthUserAdd, false); err != nil { return nil, err } // @@ -1330,7 +1330,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // login credentials. Only an admin user can change another users login // credentials or admin privilege setting. No user may change their own // admin privilege setting. - if err := conn.RegisterFunc("auth_user_change", conn.AuthUserChange, true); err != nil { + if err := conn.RegisterFunc("auth_user_change", conn.AuthUserChange, false); err != nil { return nil, err } // @@ -1339,7 +1339,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // which guarantees that there is always an admin user and hence that // the database cannot be converted into a no-authentication-required // database. - if err := conn.RegisterFunc("auth_user_delete", conn.AuthUserDelete, true); err != nil { + if err := conn.RegisterFunc("auth_user_delete", conn.AuthUserDelete, false); err != nil { return nil, err } diff --git a/sqlite3_opt_userauth.go b/sqlite3_opt_userauth.go index 2f5da0e..197938b 100644 --- a/sqlite3_opt_userauth.go +++ b/sqlite3_opt_userauth.go @@ -60,6 +60,7 @@ _sqlite3_auth_is_enabled(sqlite3* db) */ import "C" import ( + "errors" "unsafe" ) @@ -67,6 +68,11 @@ const ( SQLITE_AUTH = C.SQLITE_AUTH ) +var ( + ErrUnauthorized = errors.New("SQLITE_AUTH: Unauthorized") + ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required") +) + // Authenticate will perform an authentication of the provided username // and password against the database. // @@ -92,6 +98,9 @@ func (c *SQLiteConn) Authenticate(username, password string) error { }() rv := C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password))) + if rv == C.SQLITE_AUTH { + return ErrUnauthorized + } if rv != C.SQLITE_OK { return c.lastError() } @@ -113,6 +122,18 @@ func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { isAdmin = 1 } + rv := c.authUserAdd(username, password, isAdmin) + switch rv { + case C.SQLITE_AUTH: + return ErrAdminRequired + case C.SQLITE_OK: + return nil + default: + return c.lastError() + } +} + +func (c *SQLiteConn) authUserAdd(username, password string, admin int) int { // Allocate C Variables cuser := C.CString(username) cpass := C.CString(password) @@ -123,12 +144,7 @@ func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { C.free(unsafe.Pointer(cpass)) }() - rv := C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(isAdmin)) - if rv != C.SQLITE_OK { - return c.lastError() - } - - return nil + return int(C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(admin))) } // AuthUserChange can be used to change a users @@ -153,6 +169,9 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error }() rv := C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(isAdmin)) + if rv == C.SQLITE_AUTH { + return ErrAdminRequired + } if rv != C.SQLITE_OK { return c.lastError() } @@ -175,6 +194,9 @@ func (c *SQLiteConn) AuthUserDelete(username string) error { }() rv := C._sqlite3_user_delete(c.db, cuser) + if rv == SQLITE_AUTH { + return ErrAdminRequired + } if rv != C.SQLITE_OK { return c.lastError() } diff --git a/sqlite3_opt_userauth_omit.go b/sqlite3_opt_userauth_omit.go index 0ae92da..3d1c758 100644 --- a/sqlite3_opt_userauth_omit.go +++ b/sqlite3_opt_userauth_omit.go @@ -62,4 +62,9 @@ func (c *SQLiteConn) AuthUserDelete(username string) error { return nil } +// Check is database is protected by user authentication +func (c *SQLiteConn) AuthIsEnabled() (exists bool) { + return +} + // EOF diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index 649d10e..fcbcd56 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -9,6 +9,7 @@ package sqlite3 import ( "database/sql" + "fmt" "os" "testing" ) @@ -23,11 +24,6 @@ func TestAuthCreateDatabase(t *testing.T) { } defer db.Close() - // Ping database - if err := db.Ping(); err != nil { - t.Fatal(err) - } - var exists bool err = db.QueryRow("select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';").Scan(&exists) if err != nil { @@ -38,3 +34,51 @@ func TestAuthCreateDatabase(t *testing.T) { t.Fatal("failed to enable User Authentication") } } + +func TestAuthorization(t *testing.T) { + tempFilename := TempFilename(t) + fmt.Println(tempFilename) + //defer os.Remove(tempFilename) + + db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") + if err != nil { + t.Fatal("Failed to open database:", err) + } + + if _, err := db.Exec("select auth_user_add('user', 'user', false);"); err != nil { + t.Fatal(err) + } + + var uname string + if err := db.QueryRow("select uname from sqlite_user where uname = 'user';").Scan(&uname); err != nil { + t.Fatal(err) + } + + if uname != "user" { + t.Fatal("Failed to create normal user") + } + db.Close() + + // Re-Open Database as User + // Add User should now fail because we are not admin + db, err = sql.Open("sqlite3", "file:"+tempFilename+"?_auth_user=user&_auth_pass=user") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + // Try to create normal user + var rv string + if err := db.QueryRow("select auth_user_add('user2', 'user2', false);").Scan(&rv); err != nil { + t.Fatal(err) + } + fmt.Printf("RV: %v\n", rv) + // if rv != SQLITE_AUTH { + // t.Fatal("Succeeded creating user while not admin") + // } + + // // Try to create admin user + // if _, err := db.Exec("select auth_user_add('admin2', 'admin2', true);"); err != nil { + // t.Fatal(err) + // } +} From 0e289439a27f126dcc2fa96b2dd2d4cc9ecbbf7c Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Fri, 1 Jun 2018 11:28:04 +0200 Subject: [PATCH 07/16] Update User Authentication * Update bindings * Add user authentication sql functions Reference #579 --- sqlite3.go | 30 ++++---- sqlite3_opt_userauth.go | 136 ++++++++++++++++++++++++++--------- sqlite3_opt_userauth_omit.go | 88 ++++++++++++++++++++++- 3 files changed, 207 insertions(+), 47 deletions(-) diff --git a/sqlite3.go b/sqlite3.go index 79b96ab..4b33726 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -1292,9 +1292,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { return nil, err } - // Register Authentication Functions into connection - // - // Register Authentication function + // Register: authenticate // Authenticate will perform an authentication of the provided username // and password against the database. // @@ -1308,12 +1306,12 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // // If the SQLITE_USER table is not present in the database file, then // this interface is a harmless no-op returnning SQLITE_OK. - if err := conn.RegisterFunc("authenticate", conn.Authenticate, false); err != nil { + if err := conn.RegisterFunc("authenticate", conn.authenticate, false); err != nil { return nil, err } // - // Register AuthUserAdd - // AuthUserAdd can be used (by an admin user only) + // Register: auth_user_add + // auth_user_add can be used (by an admin user only) // to create a new user. When called on a no-authentication-required // database, this routine converts the database into an authentication- // required database, automatically makes the added user an @@ -1321,25 +1319,33 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // The AuthUserAdd only works for the "main" database, not // for any ATTACH-ed databases. Any call to AuthUserAdd by a // non-admin user results in an error. - if err := conn.RegisterFunc("auth_user_add", conn.AuthUserAdd, false); err != nil { + if err := conn.RegisterFunc("auth_user_add", conn.authUserAdd, false); err != nil { return nil, err } // - // AuthUserChange can be used to change a users + // Register: auth_user_change + // auth_user_change can be used to change a users // login credentials or admin privilege. Any user can change their own // login credentials. Only an admin user can change another users login // credentials or admin privilege setting. No user may change their own // admin privilege setting. - if err := conn.RegisterFunc("auth_user_change", conn.AuthUserChange, false); err != nil { + if err := conn.RegisterFunc("auth_user_change", conn.authUserChange, false); err != nil { return nil, err } // - // AuthUserDelete can be used (by an admin user only) + // Register: auth_user_delete + // auth_user_delete can be used (by an admin user only) // to delete a user. The currently logged-in user cannot be deleted, // which guarantees that there is always an admin user and hence that // the database cannot be converted into a no-authentication-required // database. - if err := conn.RegisterFunc("auth_user_delete", conn.AuthUserDelete, false); err != nil { + if err := conn.RegisterFunc("auth_user_delete", conn.authUserDelete, false); err != nil { + return nil, err + } + + // Register: auth_enabled + // auth_enabled can be used to check if user authentication is enabled + if err := conn.RegisterFunc("auth_enabled", conn.authEnabled, false); err != nil { return nil, err } @@ -1369,7 +1375,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } // Check if User Authentication is Enabled - authExists := conn.AuthIsEnabled() + authExists := conn.AuthEnabled() if !authExists { if err := conn.AuthUserAdd(authUser, authPass, true); err != nil { return nil, err diff --git a/sqlite3_opt_userauth.go b/sqlite3_opt_userauth.go index 197938b..94203b3 100644 --- a/sqlite3_opt_userauth.go +++ b/sqlite3_opt_userauth.go @@ -42,7 +42,7 @@ _sqlite3_user_delete(sqlite3* db, const char* zUsername) } static int -_sqlite3_auth_is_enabled(sqlite3* db) +_sqlite3_auth_enabled(sqlite3* db) { int exists = -1; @@ -87,6 +87,26 @@ var ( // If the SQLITE_USER table is not present in the database file, then // this interface is a harmless no-op returnning SQLITE_OK. func (c *SQLiteConn) Authenticate(username, password string) error { + rv := c.authenticate(username, password) + switch rv { + case C.SQLITE_ERROR, C.SQLITE_AUTH: + return ErrUnauthorized + case C.SQLITE_OK: + return nil + default: + return c.lastError() + } +} + +// authenticate provides the actual authentication to SQLite. +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) +func (c *SQLiteConn) authenticate(username, password string) int { // Allocate C Variables cuser := C.CString(username) cpass := C.CString(password) @@ -97,15 +117,7 @@ func (c *SQLiteConn) Authenticate(username, password string) error { C.free(unsafe.Pointer(cpass)) }() - rv := C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password))) - if rv == C.SQLITE_AUTH { - return ErrUnauthorized - } - if rv != C.SQLITE_OK { - return c.lastError() - } - - return nil + return int(C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password)))) } // AuthUserAdd can be used (by an admin user only) @@ -124,7 +136,7 @@ func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { rv := c.authUserAdd(username, password, isAdmin) switch rv { - case C.SQLITE_AUTH: + case C.SQLITE_ERROR, C.SQLITE_AUTH: return ErrAdminRequired case C.SQLITE_OK: return nil @@ -133,6 +145,19 @@ func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { } } +// authUserAdd enables the User Authentication if not enabled. +// Otherwise it will add a user. +// +// When user authentication is already enabled then this function +// can only be called by an admin. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) func (c *SQLiteConn) authUserAdd(username, password string, admin int) int { // Allocate C Variables cuser := C.CString(username) @@ -158,6 +183,34 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error isAdmin = 1 } + rv := c.authUserChange(username, password, isAdmin) + switch rv { + case C.SQLITE_ERROR, C.SQLITE_AUTH: + return ErrAdminRequired + case C.SQLITE_OK: + return nil + default: + return c.lastError() + } +} + +// authUserChange allows to modify a user. +// Users can change their own password. +// +// Only admins can change passwords for other users +// and modify the admin flag. +// +// The admin flag of the current logged in user cannot be changed. +// THis ensures that their is always an admin. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) +func (c *SQLiteConn) authUserChange(username, password string, admin int) int { // Allocate C Variables cuser := C.CString(username) cpass := C.CString(password) @@ -168,15 +221,7 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error C.free(unsafe.Pointer(cpass)) }() - rv := C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(isAdmin)) - if rv == C.SQLITE_AUTH { - return ErrAdminRequired - } - if rv != C.SQLITE_OK { - return c.lastError() - } - - return nil + return int(C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(admin))) } // AuthUserDelete can be used (by an admin user only) @@ -185,6 +230,29 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error // the database cannot be converted into a no-authentication-required // database. func (c *SQLiteConn) AuthUserDelete(username string) error { + rv := c.authUserDelete(username) + switch rv { + case C.SQLITE_ERROR, C.SQLITE_AUTH: + return ErrAdminRequired + case C.SQLITE_OK: + return nil + default: + return c.lastError() + } +} + +// authUserDelete can be used to delete a user. +// +// This function can only be executed by an admin. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) +func (c *SQLiteConn) authUserDelete(username string) int { // Allocate C Variables cuser := C.CString(username) @@ -193,20 +261,12 @@ func (c *SQLiteConn) AuthUserDelete(username string) error { C.free(unsafe.Pointer(cuser)) }() - rv := C._sqlite3_user_delete(c.db, cuser) - if rv == SQLITE_AUTH { - return ErrAdminRequired - } - if rv != C.SQLITE_OK { - return c.lastError() - } - - return nil + return int(C._sqlite3_user_delete(c.db, cuser)) } -// Check is database is protected by user authentication -func (c *SQLiteConn) AuthIsEnabled() (exists bool) { - rv := C._sqlite3_auth_is_enabled(c.db) +// AuthEnabled checks if the database is protected by user authentication +func (c *SQLiteConn) AuthEnabled() (exists bool) { + rv := c.authEnabled() if rv == 1 { exists = true } @@ -214,4 +274,16 @@ func (c *SQLiteConn) AuthIsEnabled() (exists bool) { return } +// authEnabled perform the actual check for user authentication. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// 0 - Disabled +// 1 - Enabled +func (c *SQLiteConn) authEnabled() int { + return int(C._sqlite3_auth_enabled(c.db)) +} + // EOF diff --git a/sqlite3_opt_userauth_omit.go b/sqlite3_opt_userauth_omit.go index 3d1c758..302cd57 100644 --- a/sqlite3_opt_userauth_omit.go +++ b/sqlite3_opt_userauth_omit.go @@ -29,6 +29,19 @@ func (c *SQLiteConn) Authenticate(username, password string) error { return nil } +// authenticate provides the actual authentication to SQLite. +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) +func (c *SQLiteConn) authenticate(username, password string) int { + // NOOP + return 0 +} + // AuthUserAdd can be used (by an admin user only) // to create a new user. When called on a no-authentication-required // database, this routine converts the database into an authentication- @@ -42,6 +55,24 @@ func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { return nil } +// authUserAdd enables the User Authentication if not enabled. +// Otherwise it will add a user. +// +// When user authentication is already enabled then this function +// can only be called by an admin. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) +func (c *SQLiteConn) authUserAdd(username, password string, admin int) int { + // NOOP + return 0 +} + // AuthUserChange can be used to change a users // login credentials or admin privilege. Any user can change their own // login credentials. Only an admin user can change another users login @@ -52,6 +83,27 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error return nil } +// authUserChange allows to modify a user. +// Users can change their own password. +// +// Only admins can change passwords for other users +// and modify the admin flag. +// +// The admin flag of the current logged in user cannot be changed. +// THis ensures that their is always an admin. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) +func (c *SQLiteConn) authUserChange(username, password string, admin int) int { + // NOOP + return 0 +} + // AuthUserDelete can be used (by an admin user only) // to delete a user. The currently logged-in user cannot be deleted, // which guarantees that there is always an admin user and hence that @@ -62,9 +114,39 @@ func (c *SQLiteConn) AuthUserDelete(username string) error { return nil } -// Check is database is protected by user authentication -func (c *SQLiteConn) AuthIsEnabled() (exists bool) { - return +// authUserDelete can be used to delete a user. +// +// This function can only be executed by an admin. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// C.SQLITE_OK (0) +// C.SQLITE_ERROR (1) +// C.SQLITE_AUTH (23) +func (c *SQLiteConn) authUserDelete(username string) int { + // NOOP + return 0 +} + +// AuthEnabled checks if the database is protected by user authentication +func (c *SQLiteConn) AuthEnabled() (exists bool) { + // NOOP + return false +} + +// authEnabled perform the actual check for user authentication. +// +// This is not exported for usage in Go. +// It is however exported for usage within SQL by the user. +// +// Returns: +// 0 - Disabled +// 1 - Enabled +func (c *SQLiteConn) authEnabled() int { + // NOOP + return 0 } // EOF From 90f966bed94ee1af23c70618a097dcf581053167 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Fri, 1 Jun 2018 11:28:29 +0200 Subject: [PATCH 08/16] Add additional tests Reference: #580 --- sqlite3_opt_userauth_test.go | 157 +++++++++++++++++++++++++++++++---- 1 file changed, 141 insertions(+), 16 deletions(-) diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index fcbcd56..4755550 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -9,7 +9,6 @@ package sqlite3 import ( "database/sql" - "fmt" "os" "testing" ) @@ -37,14 +36,19 @@ func TestAuthCreateDatabase(t *testing.T) { func TestAuthorization(t *testing.T) { tempFilename := TempFilename(t) - fmt.Println(tempFilename) - //defer os.Remove(tempFilename) + defer os.Remove(tempFilename) db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") if err != nil { t.Fatal("Failed to open database:", err) } + // Dummy Query to force connection + if _, err := db.Exec("SELECT 1;"); err != nil { + t.Fatalf("Failed to connect: %s", err) + } + + // Add normal user to database if _, err := db.Exec("select auth_user_add('user', 'user', false);"); err != nil { t.Fatal(err) } @@ -53,32 +57,153 @@ func TestAuthorization(t *testing.T) { if err := db.QueryRow("select uname from sqlite_user where uname = 'user';").Scan(&uname); err != nil { t.Fatal(err) } - if uname != "user" { t.Fatal("Failed to create normal user") } db.Close() // Re-Open Database as User - // Add User should now fail because we are not admin db, err = sql.Open("sqlite3", "file:"+tempFilename+"?_auth_user=user&_auth_pass=user") if err != nil { t.Fatal("Failed to open database:", err) } defer db.Close() - // Try to create normal user - var rv string - if err := db.QueryRow("select auth_user_add('user2', 'user2', false);").Scan(&rv); err != nil { + // Add User should now fail because we are not admin + var rv int + if err := db.QueryRow("select auth_user_add('user2', 'user2', false);").Scan(&rv); err != nil || rv == 0 { + if err != nil { + t.Fatal(err) + } + t.Fatal("Succeeded creating user, while not being admin, this is not supposed to work") + } + + // Try to create admin user + // Should also fail because we are not admin + if err := db.QueryRow("select auth_user_add('admin2', 'admin2', true);").Scan(&rv); err != nil || rv == 0 { + if err != nil { + t.Fatal(err) + } + t.Fatal("Succeeded creating admin, while not being admin, this is not supposed to work") + } +} + +func TestAuthorizationFailed(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + + db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") + if err != nil { + t.Fatal("Failed to open database:", err) + } + + // Dummy Query to force connection + if _, err := db.Exec("SELECT 1;"); err != nil { + t.Fatalf("Failed to connect: %s", err) + } + db.Close() + + db, err = sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=invalid") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + // Dummy Query to issue connection + if _, err := db.Exec("SELECT 1;"); err != nil && err != ErrUnauthorized { + t.Fatalf("Failed to connect: %s", err) + } +} + +func TestAuthUserModify(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + + var rv int + + db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") + if err != nil { + t.Fatal("Failed to open database:", err) + } + + // Dummy Query to force connection + if _, err := db.Exec("SELECT 1;"); err != nil { + t.Fatalf("Failed to connect: %s", err) + } + + if err := db.QueryRow("select auth_user_add('user', 'user', false);").Scan(&rv); err != nil || rv != 0 { + if err != nil { + t.Fatal(err) + } + t.Fatal("Failed to create normal user") + } + + if err := db.QueryRow("select auth_user_change('admin', 'nimda', true);").Scan(&rv); err != nil || rv != 0 { + if err != nil { + t.Fatal(err) + } + t.Fatal("Failed to change password") + } + db.Close() + + // Re-Connect with new credentials + db, err = sql.Open("sqlite3", "file:"+tempFilename+"?_auth_user=admin&_auth_pass=nimda") + if err != nil { + t.Fatal("Failed to open database:", err) + } + + if err := db.QueryRow("select count(uname) from sqlite_user where uname = 'admin';").Scan(&rv); err != nil { t.Fatal(err) } - fmt.Printf("RV: %v\n", rv) - // if rv != SQLITE_AUTH { - // t.Fatal("Succeeded creating user while not admin") - // } + defer db.Close() - // // Try to create admin user - // if _, err := db.Exec("select auth_user_add('admin2', 'admin2', true);"); err != nil { - // t.Fatal(err) - // } + // Dummy Query to force connection to test authorization + if _, err := db.Exec("SELECT 1;"); err != nil && err != ErrUnauthorized { + t.Fatalf("Failed to connect: %s", err) + } +} + +func TestAuthUserDelete(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + + var rv int + + db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + // Dummy Query to force connection to test authorization + if _, err := db.Exec("SELECT 1;"); err != nil { + t.Fatalf("Failed to connect: %s", err) + } + + // Add User + if _, err := db.Exec("select auth_user_add('user', 'user', false);"); err != nil { + t.Fatal(err) + } + + // Verify, their should be now 2 users + var users int + if err := db.QueryRow("select count(uname) from sqlite_user;").Scan(&users); err != nil { + t.Fatal(err) + } + if users != 2 { + t.Fatal("Failed to add user") + } + + // Delete User + if _, err := db.Exec("select auth_user_delete('user');"); err != nil { + t.Fatal(err) + } + + // Verify their should now only be 1 user remaining, the current logged in admin user + if err := db.QueryRow("select count(uname) from sqlite_user;").Scan(&users); err != nil { + t.Fatal(err) + } + if users != 1 { + t.Fatal("Failed to delete user") + } } From 11627e448371b6f065fbba2d297cc7f622f5eeac Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Fri, 1 Jun 2018 17:59:42 +0200 Subject: [PATCH 09/16] Implemented goconvey for User Authentication Tests Reference #580 --- .travis.yml | 1 + sqlite3.go | 10 +- sqlite3.goconvey | 11 + sqlite3_opt_userauth_test.go | 1239 +++++++++++++++++++++++++++++----- upgrade/package.go | 7 + upgrade/profile.goconvey | 2 + 6 files changed, 1089 insertions(+), 181 deletions(-) create mode 100644 sqlite3.goconvey create mode 100644 upgrade/package.go create mode 100644 upgrade/profile.goconvey diff --git a/.travis.yml b/.travis.yml index ba8c891..2dca239 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,6 +88,7 @@ before_install: fi - | if [[ "${GOOS}" != "windows" ]]; then + go get github.com/smartystreets/goconvey go get github.com/mattn/goveralls go get golang.org/x/tools/cmd/cover fi diff --git a/sqlite3.go b/sqlite3.go index 4b33726..e6f8c55 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -1306,7 +1306,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // // If the SQLITE_USER table is not present in the database file, then // this interface is a harmless no-op returnning SQLITE_OK. - if err := conn.RegisterFunc("authenticate", conn.authenticate, false); err != nil { + if err := conn.RegisterFunc("authenticate", conn.authenticate, true); err != nil { return nil, err } // @@ -1319,7 +1319,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // The AuthUserAdd only works for the "main" database, not // for any ATTACH-ed databases. Any call to AuthUserAdd by a // non-admin user results in an error. - if err := conn.RegisterFunc("auth_user_add", conn.authUserAdd, false); err != nil { + if err := conn.RegisterFunc("auth_user_add", conn.authUserAdd, true); err != nil { return nil, err } // @@ -1329,7 +1329,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // login credentials. Only an admin user can change another users login // credentials or admin privilege setting. No user may change their own // admin privilege setting. - if err := conn.RegisterFunc("auth_user_change", conn.authUserChange, false); err != nil { + if err := conn.RegisterFunc("auth_user_change", conn.authUserChange, true); err != nil { return nil, err } // @@ -1339,13 +1339,13 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // which guarantees that there is always an admin user and hence that // the database cannot be converted into a no-authentication-required // database. - if err := conn.RegisterFunc("auth_user_delete", conn.authUserDelete, false); err != nil { + if err := conn.RegisterFunc("auth_user_delete", conn.authUserDelete, true); err != nil { return nil, err } // Register: auth_enabled // auth_enabled can be used to check if user authentication is enabled - if err := conn.RegisterFunc("auth_enabled", conn.authEnabled, false); err != nil { + if err := conn.RegisterFunc("auth_enabled", conn.authEnabled, true); err != nil { return nil, err } diff --git a/sqlite3.goconvey b/sqlite3.goconvey new file mode 100644 index 0000000..73c2bb6 --- /dev/null +++ b/sqlite3.goconvey @@ -0,0 +1,11 @@ +// GoConvey Test Profile + +// Activate Coverage +-cover + +-run=TestUserAuthentication + +// Test Flags +-tags=sqlite_userauth + +// EOF \ No newline at end of file diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index 4755550..fbe1b92 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -9,201 +9,1088 @@ package sqlite3 import ( "database/sql" + "fmt" "os" "testing" + + . "github.com/smartystreets/goconvey/convey" ) -func TestAuthCreateDatabase(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) +func init() { - db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - var exists bool - err = db.QueryRow("select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';").Scan(&exists) - if err != nil { - t.Fatal(err) - } - - if !exists { - t.Fatal("failed to enable User Authentication") - } } -func TestAuthorization(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) +func TestUserAuthentication(t *testing.T) { + // Create database connection + var conn *SQLiteConn + sql.Register("sqlite3_with_conn", + &SQLiteDriver{ + ConnectHook: func(c *SQLiteConn) error { + conn = c + return nil + }, + }) - db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") - if err != nil { - t.Fatal("Failed to open database:", err) - } - - // Dummy Query to force connection - if _, err := db.Exec("SELECT 1;"); err != nil { - t.Fatalf("Failed to connect: %s", err) - } - - // Add normal user to database - if _, err := db.Exec("select auth_user_add('user', 'user', false);"); err != nil { - t.Fatal(err) - } - - var uname string - if err := db.QueryRow("select uname from sqlite_user where uname = 'user';").Scan(&uname); err != nil { - t.Fatal(err) - } - if uname != "user" { - t.Fatal("Failed to create normal user") - } - db.Close() - - // Re-Open Database as User - db, err = sql.Open("sqlite3", "file:"+tempFilename+"?_auth_user=user&_auth_pass=user") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - // Add User should now fail because we are not admin - var rv int - if err := db.QueryRow("select auth_user_add('user2', 'user2', false);").Scan(&rv); err != nil || rv == 0 { - if err != nil { - t.Fatal(err) + connect := func(f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) { + conn = nil // Clear connection + file = f // Copy provided file (f) => file + if file == "" { + // Create dummy file + file = TempFilename(t) } - t.Fatal("Succeeded creating user, while not being admin, this is not supposed to work") - } - // Try to create admin user - // Should also fail because we are not admin - if err := db.QueryRow("select auth_user_add('admin2', 'admin2', true);").Scan(&rv); err != nil || rv == 0 { + db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?_auth&_auth_user=%s&_auth_pass=%s", username, password)) if err != nil { - t.Fatal(err) + defer os.Remove(file) + return file, nil, nil, err } - t.Fatal("Succeeded creating admin, while not being admin, this is not supposed to work") - } -} -func TestAuthorizationFailed(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - - db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") - if err != nil { - t.Fatal("Failed to open database:", err) - } - - // Dummy Query to force connection - if _, err := db.Exec("SELECT 1;"); err != nil { - t.Fatalf("Failed to connect: %s", err) - } - db.Close() - - db, err = sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=invalid") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - // Dummy Query to issue connection - if _, err := db.Exec("SELECT 1;"); err != nil && err != ErrUnauthorized { - t.Fatalf("Failed to connect: %s", err) - } -} - -func TestAuthUserModify(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - - var rv int - - db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") - if err != nil { - t.Fatal("Failed to open database:", err) - } - - // Dummy Query to force connection - if _, err := db.Exec("SELECT 1;"); err != nil { - t.Fatalf("Failed to connect: %s", err) - } - - if err := db.QueryRow("select auth_user_add('user', 'user', false);").Scan(&rv); err != nil || rv != 0 { - if err != nil { - t.Fatal(err) + // Dummy query to force connection and database creation + // Will return ErrUnauthorized (SQLITE_AUTH) if user authentication fails + if _, err = db.Exec("SELECT 1;"); err != nil { + defer os.Remove(file) + defer db.Close() + return file, nil, nil, err } - t.Fatal("Failed to create normal user") + c = conn + + return } - if err := db.QueryRow("select auth_user_change('admin', 'nimda', true);").Scan(&rv); err != nil || rv != 0 { - if err != nil { - t.Fatal(err) - } - t.Fatal("Failed to change password") - } - db.Close() - - // Re-Connect with new credentials - db, err = sql.Open("sqlite3", "file:"+tempFilename+"?_auth_user=admin&_auth_pass=nimda") - if err != nil { - t.Fatal("Failed to open database:", err) + authEnabled := func(db *sql.DB) (exists bool, err error) { + err = db.QueryRow("select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';").Scan(&exists) + return } - if err := db.QueryRow("select count(uname) from sqlite_user where uname = 'admin';").Scan(&rv); err != nil { - t.Fatal(err) + addUser := func(db *sql.DB, username, password string, admin int) (rv int, err error) { + err = db.QueryRow("select auth_user_add(?, ?, ?);", username, password, admin).Scan(&rv) + return } - defer db.Close() - // Dummy Query to force connection to test authorization - if _, err := db.Exec("SELECT 1;"); err != nil && err != ErrUnauthorized { - t.Fatalf("Failed to connect: %s", err) - } -} - -func TestAuthUserDelete(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - - var rv int - - db, err := sql.Open("sqlite3", "file:"+tempFilename+"?_auth&_auth_user=admin&_auth_pass=admin") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - // Dummy Query to force connection to test authorization - if _, err := db.Exec("SELECT 1;"); err != nil { - t.Fatalf("Failed to connect: %s", err) - } - - // Add User - if _, err := db.Exec("select auth_user_add('user', 'user', false);"); err != nil { - t.Fatal(err) - } - - // Verify, their should be now 2 users - var users int - if err := db.QueryRow("select count(uname) from sqlite_user;").Scan(&users); err != nil { - t.Fatal(err) - } - if users != 2 { - t.Fatal("Failed to add user") - } - - // Delete User - if _, err := db.Exec("select auth_user_delete('user');"); err != nil { - t.Fatal(err) - } - - // Verify their should now only be 1 user remaining, the current logged in admin user - if err := db.QueryRow("select count(uname) from sqlite_user;").Scan(&users); err != nil { - t.Fatal(err) - } - if users != 1 { - t.Fatal("Failed to delete user") + userExists := func(db *sql.DB, username string) (rv int, err error) { + err = db.QueryRow("select count(uname) from sqlite_user where uname=?", username).Scan(&rv) + return } + + isAdmin := func(db *sql.DB, username string) (rv bool, err error) { + err = db.QueryRow("select isAdmin from sqlite_user where uname=?", username).Scan(&rv) + return + } + + modifyUser := func(db *sql.DB, username, password string, admin int) (rv int, err error) { + err = db.QueryRow("select auth_user_change(?, ?, ?);", username, password, admin).Scan(&rv) + return + } + + deleteUser := func(db *sql.DB, username string) (rv int, err error) { + err = db.QueryRow("select auth_user_delete(?);", username).Scan(&rv) + return + } + + Convey("Create Database", t, func() { + _, db, c, err := connect("", "admin", "admin") + So(db, ShouldNotBeNil) + So(c, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db.Close() + + b, err := authEnabled(db) + So(b, ShouldEqual, true) + So(err, ShouldBeNil) + + e, err := userExists(db, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + }) + + Convey("Authorization Success", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connect(f1, "admin", "admin") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("Authorization Success (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db1.Close() + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Test lower level authentication + err = c1.Authenticate("admin", "admin") + So(err, ShouldBeNil) + }) + + Convey("Authorization Failed", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Perform Invalid Authentication when we connect + // to a database + f2, db2, c2, err := connect(f1, "admin", "invalid") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldBeNil) + So(c2, ShouldBeNil) + So(err, ShouldEqual, ErrUnauthorized) + }) + + Convey("Authorization Failed (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db1.Close() + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Test lower level authentication + // We require a successful *SQLiteConn to test this. + err = c1.Authenticate("admin", "invalid") + So(err, ShouldNotBeNil) + So(err, ShouldEqual, ErrUnauthorized) + }) + + Convey("Add Admin User", t, func() { + _, db, c, err := connect("", "admin", "admin") + So(db, ShouldNotBeNil) + So(c, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db.Close() + + e, err := userExists(db, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Admin User + rv, err := addUser(db, "admin2", "admin2", 1) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + e, err = userExists(db, "admin2") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db, "admin2") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + }) + + Convey("Add Admin User (*SQLiteConn)", t, func() { + _, db, c, err := connect("", "admin", "admin") + So(db, ShouldNotBeNil) + So(c, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db.Close() + + e, err := userExists(db, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Test lower level AuthUserAdd + err = c.AuthUserAdd("admin2", "admin2", true) + So(err, ShouldBeNil) + + e, err = userExists(db, "admin2") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db, "admin2") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + }) + + Convey("Add Normal User", t, func() { + _, db, c, err := connect("", "admin", "admin") + So(db, ShouldNotBeNil) + So(c, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db.Close() + + e, err := userExists(db, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + e, err = userExists(db, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + }) + + Convey("Add Normal User (*SQLiteConn)", t, func() { + _, db, c, err := connect("", "admin", "admin") + So(db, ShouldNotBeNil) + So(c, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db.Close() + + e, err := userExists(db, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Test lower level AuthUserAdd + err = c.AuthUserAdd("user", "user", false) + So(err, ShouldBeNil) + + e, err = userExists(db, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + }) + + Convey("Add Admin User Insufficient Privileges", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + // Add Admin User + // Because 'user' is not admin + // Adding an admin user should now fail + // because we have insufficient privileges + rv, err = addUser(db2, "admin2", "admin2", 1) + So(rv, ShouldEqual, SQLITE_AUTH) + So(err, ShouldBeNil) + }) + + Convey("Add Admin User Insufficient Privileges (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + // Add Admin User + // Because 'user' is not admin + // Adding an admin user should now fail + // because we have insufficient privileges + err = c2.AuthUserAdd("admin2", "admin2", true) + So(err, ShouldNotBeNil) + So(err, ShouldEqual, ErrAdminRequired) + }) + + Convey("Add Normal User Insufficient Privileges", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + // Add Normal User + // Because 'user' is not admin + // Adding an normal user should now fail + // because we have insufficient privileges + rv, err = addUser(db2, "user2", "user2", 0) + So(rv, ShouldEqual, SQLITE_AUTH) + So(err, ShouldBeNil) + }) + + Convey("Add Normal User Insufficient Privileges (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + // Add Normal User + // Because 'user' is not admin + // Adding an normal user should now fail + // because we have insufficient privileges + // Test lower level AuthUserAdd + err = c2.AuthUserAdd("user2", "user2", false) + So(err, ShouldNotBeNil) + So(err, ShouldEqual, ErrAdminRequired) + }) + + Convey("Modify Current Connection Password", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Modify Password + rv, err := modifyUser(db1, "admin", "admin2", 1) + So(err, ShouldBeNil) + So(rv, ShouldEqual, 0) + db1.Close() + + // Reconnect with new password + f2, db2, c2, err := connect(f1, "admin", "admin2") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("Modify Current Connection Password (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db1.Close() + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Modify password through (*SQLiteConn) + err = c1.AuthUserChange("admin", "admin2", true) + So(err, ShouldBeNil) + + // Reconnect with new password + f2, db2, c2, err := connect(f1, "admin", "admin2") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("Modify Current Connection Admin Flag", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db1.Close() + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Modify Administrator Flag + // Because we are current logged in as 'admin' + // Changing our own admin flag should fail. + rv, err := modifyUser(db1, "admin", "admin", 0) + So(err, ShouldBeNil) + So(rv, ShouldEqual, SQLITE_AUTH) + }) + + Convey("Modify Current Connection Admin Flag (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db1.Close() + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Modify admin flag through (*SQLiteConn) + // Because we are current logged in as 'admin' + // Changing our own admin flag should fail. + err = c1.AuthUserChange("admin", "admin", false) + So(err, ShouldNotBeNil) + So(err, ShouldEqual, ErrAdminRequired) + }) + + Convey("Modify Other User Password", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify User + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Modify Password for user + rv, err = modifyUser(db1, "user", "user2", 0) + So(err, ShouldBeNil) + So(rv, ShouldEqual, 0) + db1.Close() + + // Reconnect as normal user with new password + f2, db2, c2, err := connect(f1, "user", "user2") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("Modify Other User Password (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify User + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Modify user password through (*SQLiteConn) + // Because we are still logged in as admin + // this should succeed. + err = c1.AuthUserChange("admin", "admin", false) + So(err, ShouldNotBeNil) + }) + + Convey("Modify Other User Admin Flag", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify User + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Modify Password for user + // Because we are logged in as admin + // This call should succeed. + rv, err = modifyUser(db1, "user", "user", 1) + So(err, ShouldBeNil) + So(rv, ShouldEqual, 0) + db1.Close() + + // Reconnect as normal user with new password + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("Modify Other User Admin Flag (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify User + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Modify user password through (*SQLiteConn) + // Because we are still logged in as admin + // this should succeed. + err = c1.AuthUserChange("user", "user", true) + So(err, ShouldBeNil) + }) + + Convey("Modify Other User Password as Non-Admin", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Add Normal User + rv, err = addUser(db1, "user2", "user2", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify 'user' + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Verify 'user2' + e, err = userExists(db1, "user2") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user2") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + // Modify password for user as normal user + // Because 'user' is not admin + // Modifying password as a normal user should now fail + // because we have insufficient privileges + rv, err = modifyUser(db2, "user2", "invalid", 0) + So(err, ShouldBeNil) + So(rv, ShouldEqual, SQLITE_AUTH) + }) + + Convey("Modify Other User Password as Non-Admin", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Add Normal User + rv, err = addUser(db1, "user2", "user2", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify 'user' + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Verify 'user2' + e, err = userExists(db1, "user2") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user2") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + // Modify user password through (*SQLiteConn) + // for 'user2' + // Because 'user' is not admin + // Modifying password as a normal user should now fail + // because we have insufficient privileges + err = c2.AuthUserChange("user2", "invalid", false) + So(err, ShouldNotBeNil) + So(err, ShouldEqual, ErrAdminRequired) + }) + + Convey("Delete User as Admin", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify 'user' + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + rv, err = deleteUser(db1, "user") + So(err, ShouldBeNil) + So(rv, ShouldEqual, 0) + + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 0) + }) + + Convey("Delete User as Admin (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify 'user' + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + err = c1.AuthUserDelete("user") + So(err, ShouldBeNil) + + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 0) + }) + + Convey("Delete User as Non-Admin", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Add Normal User + rv, err = addUser(db1, "user2", "user2", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify 'user' + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Verify 'user2' + e, err = userExists(db1, "user2") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user2") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + rv, err = deleteUser(db2, "user2") + So(err, ShouldBeNil) + So(rv, ShouldEqual, SQLITE_AUTH) + }) + + Convey("Delete User as Non-Admin (*SQLiteConn)", t, func() { + f1, db1, c1, err := connect("", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + + // Add Normal User + rv, err := addUser(db1, "user", "user", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Add Normal User + rv, err = addUser(db1, "user2", "user2", 0) + So(rv, ShouldEqual, 0) // 0 == C.SQLITE_OK + So(err, ShouldBeNil) + + // Verify 'user' + e, err = userExists(db1, "user") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + + // Verify 'user2' + e, err = userExists(db1, "user2") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err = isAdmin(db1, "user2") + So(err, ShouldBeNil) + So(a, ShouldEqual, false) + db1.Close() + + // Reconnect as normal user + f2, db2, c2, err := connect(f1, "user", "user") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + + err = c2.AuthUserDelete("user2") + So(err, ShouldNotBeNil) + So(err, ShouldEqual, ErrAdminRequired) + }) } diff --git a/upgrade/package.go b/upgrade/package.go new file mode 100644 index 0000000..2b12a77 --- /dev/null +++ b/upgrade/package.go @@ -0,0 +1,7 @@ +// Package upgrade +// +// Dummy to ensure package can be loaded +// +// This file is to avoid the following error: +// can't load package: package go-sqlite3/upgrade: build constraints exclude all Go files in go-sqlite3\upgrade +package upgrade diff --git a/upgrade/profile.goconvey b/upgrade/profile.goconvey new file mode 100644 index 0000000..55049ab --- /dev/null +++ b/upgrade/profile.goconvey @@ -0,0 +1,2 @@ +// Goconvey Profile +IGNORE From 5d4828a820ab4c8f280e1ee20688cf12609b7e4a Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Mon, 4 Jun 2018 17:04:29 +0200 Subject: [PATCH 10/16] Update TestUserAuthentication* * Update goconvey profile * Divided tests in categories Reference #580 --- sqlite3.goconvey | 5 -- sqlite3_opt_userauth_test.go | 120 +++++++++++++++++++---------------- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/sqlite3.goconvey b/sqlite3.goconvey index 73c2bb6..4a165c4 100644 --- a/sqlite3.goconvey +++ b/sqlite3.goconvey @@ -3,9 +3,4 @@ // Activate Coverage -cover --run=TestUserAuthentication - -// Test Flags --tags=sqlite_userauth - // EOF \ No newline at end of file diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index fbe1b92..a71716c 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -16,13 +16,19 @@ import ( . "github.com/smartystreets/goconvey/convey" ) +var ( + conn *SQLiteConn + connect func(t *testing.T, f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) + authEnabled func(db *sql.DB) (exists bool, err error) + addUser func(db *sql.DB, username, password string, admin int) (rv int, err error) + userExists func(db *sql.DB, username string) (rv int, err error) + isAdmin func(db *sql.DB, username string) (rv bool, err error) + modifyUser func(db *sql.DB, username, password string, admin int) (rv int, err error) + deleteUser func(db *sql.DB, username string) (rv int, err error) +) + func init() { - -} - -func TestUserAuthentication(t *testing.T) { // Create database connection - var conn *SQLiteConn sql.Register("sqlite3_with_conn", &SQLiteDriver{ ConnectHook: func(c *SQLiteConn) error { @@ -31,7 +37,7 @@ func TestUserAuthentication(t *testing.T) { }, }) - connect := func(f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) { + connect = func(t *testing.T, f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) { conn = nil // Clear connection file = f // Copy provided file (f) => file if file == "" { @@ -57,38 +63,40 @@ func TestUserAuthentication(t *testing.T) { return } - authEnabled := func(db *sql.DB) (exists bool, err error) { + authEnabled = func(db *sql.DB) (exists bool, err error) { err = db.QueryRow("select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';").Scan(&exists) return } - addUser := func(db *sql.DB, username, password string, admin int) (rv int, err error) { + addUser = func(db *sql.DB, username, password string, admin int) (rv int, err error) { err = db.QueryRow("select auth_user_add(?, ?, ?);", username, password, admin).Scan(&rv) return } - userExists := func(db *sql.DB, username string) (rv int, err error) { + userExists = func(db *sql.DB, username string) (rv int, err error) { err = db.QueryRow("select count(uname) from sqlite_user where uname=?", username).Scan(&rv) return } - isAdmin := func(db *sql.DB, username string) (rv bool, err error) { + isAdmin = func(db *sql.DB, username string) (rv bool, err error) { err = db.QueryRow("select isAdmin from sqlite_user where uname=?", username).Scan(&rv) return } - modifyUser := func(db *sql.DB, username, password string, admin int) (rv int, err error) { + modifyUser = func(db *sql.DB, username, password string, admin int) (rv int, err error) { err = db.QueryRow("select auth_user_change(?, ?, ?);", username, password, admin).Scan(&rv) return } - deleteUser := func(db *sql.DB, username string) (rv int, err error) { + deleteUser = func(db *sql.DB, username string) (rv int, err error) { err = db.QueryRow("select auth_user_delete(?);", username).Scan(&rv) return } +} +func TestUserAuthentication(t *testing.T) { Convey("Create Database", t, func() { - _, db, c, err := connect("", "admin", "admin") + _, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) @@ -108,7 +116,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Authorization Success", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -124,7 +132,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Preform authentication - f2, db2, c2, err := connect(f1, "admin", "admin") + f2, db2, c2, err := connect(t, f1, "admin", "admin") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -134,7 +142,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Authorization Success (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -155,7 +163,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Authorization Failed", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -171,7 +179,7 @@ func TestUserAuthentication(t *testing.T) { // Perform Invalid Authentication when we connect // to a database - f2, db2, c2, err := connect(f1, "admin", "invalid") + f2, db2, c2, err := connect(t, f1, "admin", "invalid") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldBeNil) @@ -180,7 +188,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Authorization Failed (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -201,9 +209,11 @@ func TestUserAuthentication(t *testing.T) { So(err, ShouldNotBeNil) So(err, ShouldEqual, ErrUnauthorized) }) +} +func TestUserAuthenticationAddUser(t *testing.T) { Convey("Add Admin User", t, func() { - _, db, c, err := connect("", "admin", "admin") + _, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) @@ -232,7 +242,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Add Admin User (*SQLiteConn)", t, func() { - _, db, c, err := connect("", "admin", "admin") + _, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) @@ -260,7 +270,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Add Normal User", t, func() { - _, db, c, err := connect("", "admin", "admin") + _, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) @@ -289,7 +299,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Add Normal User (*SQLiteConn)", t, func() { - _, db, c, err := connect("", "admin", "admin") + _, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) @@ -317,7 +327,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Add Admin User Insufficient Privileges", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -346,7 +356,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -364,7 +374,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Add Admin User Insufficient Privileges (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -393,7 +403,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -411,7 +421,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Add Normal User Insufficient Privileges", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -440,7 +450,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -458,7 +468,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Add Normal User Insufficient Privileges (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -487,7 +497,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -504,9 +514,11 @@ func TestUserAuthentication(t *testing.T) { So(err, ShouldNotBeNil) So(err, ShouldEqual, ErrAdminRequired) }) +} +func TestUserAuthenticationModifyUser(t *testing.T) { Convey("Modify Current Connection Password", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -527,7 +539,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect with new password - f2, db2, c2, err := connect(f1, "admin", "admin2") + f2, db2, c2, err := connect(t, f1, "admin", "admin2") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -537,7 +549,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Current Connection Password (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -557,7 +569,7 @@ func TestUserAuthentication(t *testing.T) { So(err, ShouldBeNil) // Reconnect with new password - f2, db2, c2, err := connect(f1, "admin", "admin2") + f2, db2, c2, err := connect(t, f1, "admin", "admin2") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -567,7 +579,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Current Connection Admin Flag", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -591,7 +603,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Current Connection Admin Flag (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -615,7 +627,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Other User Password", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -650,7 +662,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user with new password - f2, db2, c2, err := connect(f1, "user", "user2") + f2, db2, c2, err := connect(t, f1, "user", "user2") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -660,7 +672,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Other User Password (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -696,7 +708,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Other User Admin Flag", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -733,7 +745,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user with new password - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -743,7 +755,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Other User Admin Flag (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -779,7 +791,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Other User Password as Non-Admin", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -823,7 +835,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -841,7 +853,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Modify Other User Password as Non-Admin", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -885,7 +897,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -902,9 +914,11 @@ func TestUserAuthentication(t *testing.T) { So(err, ShouldNotBeNil) So(err, ShouldEqual, ErrAdminRequired) }) +} +func TestUserAuthenticationDeleteUser(t *testing.T) { Convey("Delete User as Admin", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -942,7 +956,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Delete User as Admin (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -979,7 +993,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Delete User as Non-Admin", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -1023,7 +1037,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) @@ -1037,7 +1051,7 @@ func TestUserAuthentication(t *testing.T) { }) Convey("Delete User as Non-Admin (*SQLiteConn)", t, func() { - f1, db1, c1, err := connect("", "admin", "admin") + f1, db1, c1, err := connect(t, "", "admin", "admin") So(f1, ShouldNotBeBlank) So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) @@ -1081,7 +1095,7 @@ func TestUserAuthentication(t *testing.T) { db1.Close() // Reconnect as normal user - f2, db2, c2, err := connect(f1, "user", "user") + f2, db2, c2, err := connect(t, f1, "user", "user") So(f2, ShouldNotBeBlank) So(f1, ShouldEqual, f2) So(db2, ShouldNotBeNil) From 086629727d194cc949b7abb80fc5e62538c0a5f0 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Tue, 5 Jun 2018 12:20:54 +0200 Subject: [PATCH 11/16] Add inital documentation References: #581 --- README.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/README.md b/README.md index f74794e..34d5569 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ Supported Golang version: - [Mac OSX](#mac-osx) - [Windows](#windows) - [Errors](#errors) +- [User Authentication](#user-authentication) + - [Compile](#compile) + - [Usage](#usage) + - - [Extensions](#extensions) - [Spatialite](#spatialite) - [FAQ](#faq) @@ -76,6 +80,9 @@ Boolean values can be one of: | Name | Key | Value(s) | Description | |------|-----|----------|-------------| +| UA - Create | `_auth` | - | Create User Authentication, for more information see [User Authentication](#user-authentication) | +| UA - Username | `_auth_user` | - | Username for User Authentication, for more information see [User Authentication](#user-authentication) | +| UA - Password | `_auth_pass` | - | Password for User Authentication, for more information see [User Authentication](#user-authentication) | | Auto Vacuum | `_auto_vacuum` \| `_vacuum` |
  • `0` \| `none`
  • `1` \| `full`
  • `2` \| `incremental`
| For more information see [PRAGMA auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) | | Busy Timeout | `_busy_timeout` \| `_timeout` | `int` | Specify value for sqlite3_busy_timeout. For more information see [PRAGMA busy_timeout](https://www.sqlite.org/pragma.html#pragma_busy_timeout) | | Case Sensitive LIKE | `_case_sensitive_like` \| `_cslike` | `boolean` | For more information see [PRAGMA case_sensitive_like](https://www.sqlite.org/pragma.html#pragma_case_sensitive_like) | @@ -144,6 +151,7 @@ go build --tags "icu json1 fts5 secure_delete" | Secure Delete | sqlite_secure_delete | This compile-time option changes the default setting of the secure_delete pragma.

When this option is not used, secure_delete defaults to off. When this option is present, secure_delete defaults to on.

The secure_delete setting causes deleted content to be overwritten with zeros. There is a small performance penalty since additional I/O must occur.

On the other hand, secure_delete can prevent fragments of sensitive information from lingering in unused parts of the database file after it has been deleted. See the documentation on the secure_delete pragma for additional information | | Secure Delete (FAST) | sqlite_secure_delete_fast | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) | | Tracing / Debug | sqlite_trace | Activate trace functions | +| User Authentication | sqlite_userauth | SQLite User Authentication see [User Authentication](#user-authentication) for more information. | # Compilation @@ -303,6 +311,93 @@ For example the TDM-GCC Toolchain can be found [here](ttps://sourceforge.net/pro go install github.com/mattn/go-sqlite3 ``` +# User Authentication + +This package supports the SQLite User Authentication module. + +## Compile + +To use the User authentication module the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features). + +## Usage + +### Create protected database + +To create a database protected by user authentication provide the following argument to the connection string `_auth`. +This will enable user authentication within the database. This option however requires two additional arguments: + +- `_auth_user` +- `_auth_pass` + +When `_auth` is present on the connection string user authentication will be enabled and the provided user will be created +as an `admin` user. After initial creation, the parameter `_auth` has no effect anymore and can be omitted from the connection string. + +Example connection string: + +Create an user authentication database with user `admin` and password `admin`. + +`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin` + +### Restrictions + +Operations on the database regarding to user management can only be preformed by an administrator user. + +### Support + +The user authentication supports two kinds of users + +- administrators +- regular users + +### User Management + +User management can be done by directly using the `*SQLiteConn` or by SQL. + +#### SQL + +The following sql functions are available for user management. + +| Function | Arguments | Description | +|----------|-----------|-------------| +| `authenticate` | username `string`, password `string` | Will authenticate an user, this is done by the connection; and should not be used manually. | +| `auth_user_add` | username `string`, password `string`, admin `int` | This function will add an user to the database.
if the database is not protected by user authentication it will enable it. Argument `admin` is an integer identifying if the added user should be an administrator. Only Administrators can add administrators. | +| `auth_user_change` | username `string`, password `string`, admin `int` | Function to modify an user. Users can change their own password, but only an administrator can change the administrator flag. | +| `authUserDelete` | username `string` | Delete an user from the database. Can only be used by an administrator. The current logged in administrator cannot be deleted. This is to make sure their is always an administrator remaining. | + +These functions will return an integer. + +- 0 (SQLITE_OK) +- 23 (SQLITE_AUTH) Failed to perform due to authentication or insufficient privileges + +##### Examples + +```sql +// Autheticate user +// Create Admin User +SELECT auth_user_add('admin2', 'admin2', 1); + +// Change password for user +SELECT auth_user_change('user', 'userpassword', 0); + +// Delete user +SELECT user_delete('user'); +``` + +#### *SQLiteConn + +The following functions are available for User authentication from the `*SQLiteConn`. + +| Function | Description | +|----------|-------------| +| `Authenticate(username, password string) error` | Authenticate user | +| `AuthUserAdd(username, password string, admin bool) error` | Add user | +| `AuthUserChange(username, password string, admin bool) error` | Modify user | +| `AuthUserDelete(username string) error` | Delete user | + +### Attached database + +When using attached databases. SQLite will use the authentication from the `main` database for the attached database(s). + # Extensions If you want your own extension to be listed here or you want to add a reference to an extension; please submit an Issue for this. From 55a3551baaff29bf1b7745517114732e0aab034a Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Tue, 5 Jun 2018 13:28:26 +0200 Subject: [PATCH 12/16] Add: goconvey to Travis-CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2dca239..91b61c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,8 +87,8 @@ before_install: brew upgrade icu4c fi - | + go get github.com/smartystreets/goconvey if [[ "${GOOS}" != "windows" ]]; then - go get github.com/smartystreets/goconvey go get github.com/mattn/goveralls go get golang.org/x/tools/cmd/cover fi From f46bde7099328de86e04cc87832ec5abf025c4a2 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Tue, 5 Jun 2018 13:43:07 +0200 Subject: [PATCH 13/16] Update User Authentication Documentation References: #581 --- README.md | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 34d5569..ca84ffd 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,10 @@ Boolean values can be one of: | Name | Key | Value(s) | Description | |------|-----|----------|-------------| | UA - Create | `_auth` | - | Create User Authentication, for more information see [User Authentication](#user-authentication) | -| UA - Username | `_auth_user` | - | Username for User Authentication, for more information see [User Authentication](#user-authentication) | -| UA - Password | `_auth_pass` | - | Password for User Authentication, for more information see [User Authentication](#user-authentication) | +| UA - Username | `_auth_user` | `string` | Username for User Authentication, for more information see [User Authentication](#user-authentication) | +| UA - Password | `_auth_pass` | `string` | Password for User Authentication, for more information see [User Authentication](#user-authentication) | +| UA - Crypt | `_auth_crypt` |
  • SHA1
  • SSHA1
  • SHA256
  • SSHA256
  • SHA384
  • SSHA384
  • SHA512
  • SSHA512
| Password encoder to use for User Authentication, for more information see [User Authentication](#user-authentication) | +| UA - Salt | `_auth_salt` | `string` | Salt to use if the configure password encoder requires a salt, for User Authentication, for more information see [User Authentication](#user-authentication) | | Auto Vacuum | `_auto_vacuum` \| `_vacuum` |
  • `0` \| `none`
  • `1` \| `full`
  • `2` \| `incremental`
| For more information see [PRAGMA auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) | | Busy Timeout | `_busy_timeout` \| `_timeout` | `int` | Specify value for sqlite3_busy_timeout. For more information see [PRAGMA busy_timeout](https://www.sqlite.org/pragma.html#pragma_busy_timeout) | | Case Sensitive LIKE | `_case_sensitive_like` \| `_cslike` | `boolean` | For more information see [PRAGMA case_sensitive_like](https://www.sqlite.org/pragma.html#pragma_case_sensitive_like) | @@ -338,6 +340,30 @@ Create an user authentication database with user `admin` and password `admin`. `file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin` +Create an user authentication database with user `admin` and password `admin` and use `SHA1` for the password encoding. + +`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin&_auth_crypt=sha1` + +### Password Encoding + +The passwords within the user authentication module of SQLite are encoded with the SQLite function `sqlite_cryp`. +This function uses a ceasar-cypher which is quite insecure. +This library provides several additional password encoders which can be configured through the connection string. + +The password cypher can be configured with the key `_auth_crypt`. And if the configured password encoder also requires an +salt this can be configured with `_auth_salt`. + +#### Available Encoders + +- SHA1 +- SSHA1 (Salted SHA1) +- SHA256 +- SSHA256 (salted SHA256) +- SHA384 +- SSHA384 (salted SHA384) +- SHA512 +- SSHA512 (salted SHA512) + ### Restrictions Operations on the database regarding to user management can only be preformed by an administrator user. From 9b30110b833d7ccf265e47c0c1e9ed2ce516a449 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Tue, 5 Jun 2018 13:43:35 +0200 Subject: [PATCH 14/16] ADD: sqlite_auth to goconvey test suite --- sqlite3.goconvey | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sqlite3.goconvey b/sqlite3.goconvey index 4a165c4..295b895 100644 --- a/sqlite3.goconvey +++ b/sqlite3.goconvey @@ -3,4 +3,6 @@ // Activate Coverage -cover +-tags=sqlite_userauth + // EOF \ No newline at end of file From 7337e65c27313aec52f96e6da520acd2fe48c00f Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Tue, 5 Jun 2018 13:45:32 +0200 Subject: [PATCH 15/16] ADD: User Authentication Password Encoders Allow user to choose how to encode passwords with connection string overrides of embedded `sqlite_crypt` function. --- sqlite3.go | 58 +++++++++ sqlite3_func_crypt.go | 120 +++++++++++++++++ sqlite3_opt_userauth_test.go | 244 +++++++++++++++++++++++++++++++++++ 3 files changed, 422 insertions(+) create mode 100644 sqlite3_func_crypt.go diff --git a/sqlite3.go b/sqlite3.go index e6f8c55..979aedc 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -894,6 +894,8 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { authCreate := false authUser := "" authPass := "" + authCrypt := "" + authSalt := "" mutex := C.int(C.SQLITE_OPEN_FULLMUTEX) txlock := "BEGIN" @@ -929,6 +931,12 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { if val := params.Get("_auth_pass"); val != "" { authPass = val } + if val := params.Get("_auth_crypt"); val != "" { + authCrypt = val + } + if val := params.Get("_auth_salt"); val != "" { + authSalt = val + } // _loc if val := params.Get("_loc"); val != "" { @@ -1287,6 +1295,56 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // Create connection to SQLite conn := &SQLiteConn{db: db, loc: loc, txlock: txlock} + // Password Cipher has to be registerd before authentication + if len(authCrypt) > 0 { + switch strings.ToUpper(authCrypt) { + case "SHA1": + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA1, true); err != nil { + return nil, fmt.Errorf("CryptEncoderSHA1: %s", err) + } + case "SSHA1": + if len(authSalt) == 0 { + return nil, fmt.Errorf("_auth_crypt=ssha1, requires _auth_salt") + } + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA1(authSalt), true); err != nil { + return nil, fmt.Errorf("CryptEncoderSSHA1: %s", err) + } + case "SHA256": + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA256, true); err != nil { + return nil, fmt.Errorf("CryptEncoderSHA256: %s", err) + } + case "SSHA256": + if len(authSalt) == 0 { + return nil, fmt.Errorf("_auth_crypt=ssha256, requires _auth_salt") + } + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA256(authSalt), true); err != nil { + return nil, fmt.Errorf("CryptEncoderSSHA256: %s", err) + } + case "SHA384": + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA384, true); err != nil { + return nil, fmt.Errorf("CryptEncoderSHA384: %s", err) + } + case "SSHA384": + if len(authSalt) == 0 { + return nil, fmt.Errorf("_auth_crypt=ssha384, requires _auth_salt") + } + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA384(authSalt), true); err != nil { + return nil, fmt.Errorf("CryptEncoderSSHA384: %s", err) + } + case "SHA512": + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA512, true); err != nil { + return nil, fmt.Errorf("CryptEncoderSHA512: %s", err) + } + case "SSHA512": + if len(authSalt) == 0 { + return nil, fmt.Errorf("_auth_crypt=ssha512, requires _auth_salt") + } + if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA512(authSalt), true); err != nil { + return nil, fmt.Errorf("CryptEncoderSSHA512: %s", err) + } + } + } + // Preform Authentication if err := conn.Authenticate(authUser, authPass); err != nil { return nil, err diff --git a/sqlite3_func_crypt.go b/sqlite3_func_crypt.go new file mode 100644 index 0000000..3774a97 --- /dev/null +++ b/sqlite3_func_crypt.go @@ -0,0 +1,120 @@ +// Copyright (C) 2018 G.J.R. Timmer . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package sqlite3 + +import ( + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" +) + +// This file provides several different implementations for the +// default embedded sqlite_crypt function. +// This function is uses a ceasar-cypher by default +// and is used within the UserAuthentication module to encode +// the password. +// +// The provided functions can be used as an overload to the sqlite_crypt +// function through the use of the RegisterFunc on the connection. +// +// Because the functions can serv a purpose to an end-user +// without using the UserAuthentication module +// the functions are default compiled in. +// +// From SQLITE3 - user-auth.txt +// The sqlite_user.pw field is encoded by a built-in SQL function +// "sqlite_crypt(X,Y)". The two arguments are both BLOBs. The first argument +// is the plaintext password supplied to the sqlite3_user_authenticate() +// interface. The second argument is the sqlite_user.pw value and is supplied +// so that the function can extract the "salt" used by the password encoder. +// The result of sqlite_crypt(X,Y) is another blob which is the value that +// ends up being stored in sqlite_user.pw. To verify credentials X supplied +// by the sqlite3_user_authenticate() routine, SQLite runs: +// +// sqlite_user.pw == sqlite_crypt(X, sqlite_user.pw) +// +// To compute an appropriate sqlite_user.pw value from a new or modified +// password X, sqlite_crypt(X,NULL) is run. A new random salt is selected +// when the second argument is NULL. +// +// The built-in version of of sqlite_crypt() uses a simple Ceasar-cypher +// which prevents passwords from being revealed by searching the raw database +// for ASCII text, but is otherwise trivally broken. For better password +// security, the database should be encrypted using the SQLite Encryption +// Extension or similar technology. Or, the application can use the +// sqlite3_create_function() interface to provide an alternative +// implementation of sqlite_crypt() that computes a stronger password hash, +// perhaps using a cryptographic hash function like SHA1. + +// CryptEncoderSHA1 encodes a password with SHA1 +func CryptEncoderSHA1(pass []byte, hash interface{}) []byte { + h := sha1.Sum(pass) + return h[:] +} + +// CryptEncoderSSHA1 encodes a password with SHA1 with the +// configured salt. +func CryptEncoderSSHA1(salt string) func(pass []byte, hash interface{}) []byte { + return func(pass []byte, hash interface{}) []byte { + s := []byte(salt) + p := append(pass, s...) + h := sha1.Sum(p) + return h[:] + } +} + +// CryptEncoderSHA256 encodes a password with SHA256 +func CryptEncoderSHA256(pass []byte, hash interface{}) []byte { + h := sha256.Sum256(pass) + return h[:] +} + +// CryptEncoderSSHA256 encodes a password with SHA256 +// with the configured salt +func CryptEncoderSSHA256(salt string) func(pass []byte, hash interface{}) []byte { + return func(pass []byte, hash interface{}) []byte { + s := []byte(salt) + p := append(pass, s...) + h := sha256.Sum256(p) + return h[:] + } +} + +// CryptEncoderSHA384 encodes a password with SHA256 +func CryptEncoderSHA384(pass []byte, hash interface{}) []byte { + h := sha512.Sum384(pass) + return h[:] +} + +// CryptEncoderSSHA384 encodes a password with SHA256 +// with the configured salt +func CryptEncoderSSHA384(salt string) func(pass []byte, hash interface{}) []byte { + return func(pass []byte, hash interface{}) []byte { + s := []byte(salt) + p := append(pass, s...) + h := sha512.Sum384(p) + return h[:] + } +} + +// CryptEncoderSHA512 encodes a password with SHA256 +func CryptEncoderSHA512(pass []byte, hash interface{}) []byte { + h := sha512.Sum512(pass) + return h[:] +} + +// CryptEncoderSSHA512 encodes a password with SHA256 +// with the configured salt +func CryptEncoderSSHA512(salt string) func(pass []byte, hash interface{}) []byte { + return func(pass []byte, hash interface{}) []byte { + s := []byte(salt) + p := append(pass, s...) + h := sha512.Sum512(p) + return h[:] + } +} + +// EOF diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index a71716c..cd05b05 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -1108,3 +1108,247 @@ func TestUserAuthenticationDeleteUser(t *testing.T) { So(err, ShouldEqual, ErrAdminRequired) }) } + +func TestUserAuthenticationEncoder(t *testing.T) { + connectWithCrypt := func(t *testing.T, f string, username, password string, crypt string, salt string) (file string, db *sql.DB, c *SQLiteConn, err error) { + conn = nil // Clear connection + file = f // Copy provided file (f) => file + if file == "" { + // Create dummy file + file = TempFilename(t) + } + + db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?_auth&_auth_user=%s&_auth_pass=%s&_auth_crypt=%s&_auth_salt=%s", username, password, crypt, salt)) + if err != nil { + defer os.Remove(file) + return file, nil, nil, err + } + + // Dummy query to force connection and database creation + // Will return ErrUnauthorized (SQLITE_AUTH) if user authentication fails + if _, err = db.Exec("SELECT 1;"); err != nil { + defer os.Remove(file) + defer db.Close() + return file, nil, nil, err + } + c = conn + + return + } + + Convey("SHA1 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha1", "") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha1", "") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("SSHA1 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha1", "salted") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha1", "salted") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("SHA256 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha256", "") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha256", "") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("SSHA256 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha256", "salted") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha256", "salted") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("SHA384 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha384", "") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha384", "") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("SSHA384 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha384", "salted") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha384", "salted") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("SHA512 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "sha512", "") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "sha512", "") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) + + Convey("SSHA512 Encoder", t, func() { + f1, db1, c1, err := connectWithCrypt(t, "", "admin", "admin", "ssha512", "salted") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + + e, err := userExists(db1, "admin") + So(err, ShouldBeNil) + So(e, ShouldEqual, 1) + + a, err := isAdmin(db1, "admin") + So(err, ShouldBeNil) + So(a, ShouldEqual, true) + db1.Close() + + // Preform authentication + f2, db2, c2, err := connectWithCrypt(t, f1, "admin", "admin", "ssha512", "salted") + So(f2, ShouldNotBeBlank) + So(f1, ShouldEqual, f2) + So(db2, ShouldNotBeNil) + So(c2, ShouldNotBeNil) + So(err, ShouldBeNil) + defer db2.Close() + }) +} From 4ba9507ff0d0cf5639b0e85b68089154f8213565 Mon Sep 17 00:00:00 2001 From: Gert-Jan Timmer Date: Tue, 5 Jun 2018 13:45:46 +0200 Subject: [PATCH 16/16] Fix: test suite remove created files --- sqlite3_opt_userauth_test.go | 38 +++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index cd05b05..1a29575 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -96,11 +96,12 @@ func init() { func TestUserAuthentication(t *testing.T) { Convey("Create Database", t, func() { - _, db, c, err := connect(t, "", "admin", "admin") + f, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) defer db.Close() + defer os.Remove(f) b, err := authEnabled(db) So(b, ShouldEqual, true) @@ -121,6 +122,7 @@ func TestUserAuthentication(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -147,6 +149,7 @@ func TestUserAuthentication(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) defer db1.Close() e, err := userExists(db1, "admin") @@ -168,6 +171,7 @@ func TestUserAuthentication(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -176,6 +180,7 @@ func TestUserAuthentication(t *testing.T) { a, err := isAdmin(db1, "admin") So(err, ShouldBeNil) So(a, ShouldEqual, true) + db1.Close() // Perform Invalid Authentication when we connect // to a database @@ -193,6 +198,7 @@ func TestUserAuthentication(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) defer db1.Close() e, err := userExists(db1, "admin") @@ -213,10 +219,11 @@ func TestUserAuthentication(t *testing.T) { func TestUserAuthenticationAddUser(t *testing.T) { Convey("Add Admin User", t, func() { - _, db, c, err := connect(t, "", "admin", "admin") + f, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f) defer db.Close() e, err := userExists(db, "admin") @@ -242,10 +249,11 @@ func TestUserAuthenticationAddUser(t *testing.T) { }) Convey("Add Admin User (*SQLiteConn)", t, func() { - _, db, c, err := connect(t, "", "admin", "admin") + f, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f) defer db.Close() e, err := userExists(db, "admin") @@ -270,10 +278,11 @@ func TestUserAuthenticationAddUser(t *testing.T) { }) Convey("Add Normal User", t, func() { - _, db, c, err := connect(t, "", "admin", "admin") + f, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f) defer db.Close() e, err := userExists(db, "admin") @@ -299,10 +308,11 @@ func TestUserAuthenticationAddUser(t *testing.T) { }) Convey("Add Normal User (*SQLiteConn)", t, func() { - _, db, c, err := connect(t, "", "admin", "admin") + f, db, c, err := connect(t, "", "admin", "admin") So(db, ShouldNotBeNil) So(c, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f) defer db.Close() e, err := userExists(db, "admin") @@ -332,6 +342,7 @@ func TestUserAuthenticationAddUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -379,6 +390,7 @@ func TestUserAuthenticationAddUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -426,6 +438,7 @@ func TestUserAuthenticationAddUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -473,6 +486,7 @@ func TestUserAuthenticationAddUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -523,6 +537,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -554,6 +569,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) defer db1.Close() e, err := userExists(db1, "admin") @@ -584,6 +600,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) defer db1.Close() e, err := userExists(db1, "admin") @@ -608,6 +625,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) defer db1.Close() e, err := userExists(db1, "admin") @@ -632,6 +650,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -677,6 +696,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -713,6 +733,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -760,6 +781,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -796,6 +818,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -858,6 +881,7 @@ func TestUserAuthenticationModifyUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -923,6 +947,7 @@ func TestUserAuthenticationDeleteUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -961,6 +986,7 @@ func TestUserAuthenticationDeleteUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -998,6 +1024,7 @@ func TestUserAuthenticationDeleteUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil) @@ -1056,6 +1083,7 @@ func TestUserAuthenticationDeleteUser(t *testing.T) { So(db1, ShouldNotBeNil) So(c1, ShouldNotBeNil) So(err, ShouldBeNil) + defer os.Remove(f1) e, err := userExists(db1, "admin") So(err, ShouldBeNil)