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/.travis.yml b/.travis.yml index ba8c891..91b61c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,6 +87,7 @@ before_install: brew upgrade icu4c fi - | + go get github.com/smartystreets/goconvey if [[ "${GOOS}" != "windows" ]]; then go get github.com/mattn/goveralls go get golang.org/x/tools/cmd/cover diff --git a/README.md b/README.md index f74794e..ca84ffd 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,11 @@ 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` | `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` | | 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` | | 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 +153,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 +313,117 @@ 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` + +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. + +### 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. 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 f9bbf12..c63a5b1 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -891,6 +891,11 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // Options var loc *time.Location + authCreate := false + authUser := "" + authPass := "" + authCrypt := "" + authSalt := "" mutex := C.int(C.SQLITE_OPEN_FULLMUTEX) txlock := "BEGIN" @@ -916,6 +921,23 @@ 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 + } + 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 != "" { switch strings.ToLower(val) { @@ -1248,7 +1270,149 @@ 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} + + // 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 + } + + // Register: authenticate + // 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: 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 + // 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 + } + // + // 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, true); err != nil { + return nil, err + } + // + // 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, 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, 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 +1420,27 @@ 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'") + } + + // Check if User Authentication is Enabled + authExists := conn.AuthEnabled() + 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 +1532,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.goconvey b/sqlite3.goconvey new file mode 100644 index 0000000..295b895 --- /dev/null +++ b/sqlite3.goconvey @@ -0,0 +1,8 @@ +// GoConvey Test Profile + +// Activate Coverage +-cover + +-tags=sqlite_userauth + +// EOF \ No newline at end of file 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_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..94203b3 --- /dev/null +++ b/sqlite3_opt_userauth.go @@ -0,0 +1,289 @@ +// 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); +} + +static int +_sqlite3_auth_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 ( + "errors" + "unsafe" +) + +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. +// +// 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.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) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + C.free(unsafe.Pointer(cpass)) + }() + + return int(C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password)))) +} + +// 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.authUserAdd(username, password, isAdmin) + switch rv { + case C.SQLITE_ERROR, C.SQLITE_AUTH: + return ErrAdminRequired + case C.SQLITE_OK: + return nil + default: + return c.lastError() + } +} + +// 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) + cpass := C.CString(password) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + C.free(unsafe.Pointer(cpass)) + }() + + 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 +// 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.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) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + C.free(unsafe.Pointer(cpass)) + }() + + 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) +// 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.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) + + // Free C Variables + defer func() { + C.free(unsafe.Pointer(cuser)) + }() + + return int(C._sqlite3_user_delete(c.db, cuser)) +} + +// AuthEnabled checks if the database is protected by user authentication +func (c *SQLiteConn) AuthEnabled() (exists bool) { + rv := c.authEnabled() + if rv == 1 { + exists = true + } + + 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 new file mode 100644 index 0000000..302cd57 --- /dev/null +++ b/sqlite3_opt_userauth_omit.go @@ -0,0 +1,152 @@ +// 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 +} + +// 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- +// 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 +} + +// 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 +// 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 +} + +// 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 +// the database cannot be converted into a no-authentication-required +// database. +func (c *SQLiteConn) AuthUserDelete(username string) error { + // NOOP + return nil +} + +// 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 diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go new file mode 100644 index 0000000..1a29575 --- /dev/null +++ b/sqlite3_opt_userauth_test.go @@ -0,0 +1,1382 @@ +// 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" + "os" + "testing" + + . "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() { + // Create database connection + sql.Register("sqlite3_with_conn", + &SQLiteDriver{ + ConnectHook: func(c *SQLiteConn) error { + conn = c + return nil + }, + }) + + 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 == "" { + // Create dummy file + file = TempFilename(t) + } + + db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?_auth&_auth_user=%s&_auth_pass=%s", username, password)) + 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 + } + + 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) { + err = db.QueryRow("select auth_user_add(?, ?, ?);", username, password, admin).Scan(&rv) + return + } + + 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 + } +} + +func TestUserAuthentication(t *testing.T) { + Convey("Create Database", t, func() { + 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) + 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(t, "", "admin", "admin") + 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 := connect(t, 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(t, "", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + 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(t, "", "admin", "admin") + 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() + + // Perform Invalid Authentication when we connect + // to a database + f2, db2, c2, err := connect(t, 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(t, "", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + 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) + }) +} + +func TestUserAuthenticationAddUser(t *testing.T) { + Convey("Add Admin User", t, func() { + 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") + 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() { + 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") + 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() { + 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") + 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() { + 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") + 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(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + 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) + + // 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(t, 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) + }) +} + +func TestUserAuthenticationModifyUser(t *testing.T) { + Convey("Modify Current Connection Password", t, func() { + f1, db1, c1, err := connect(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + 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(t, 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(t, "", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + 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(t, "", "admin", "admin") + So(f1, ShouldNotBeBlank) + So(db1, ShouldNotBeNil) + So(c1, ShouldNotBeNil) + So(err, ShouldBeNil) + defer os.Remove(f1) + 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(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + 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) + + // 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(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + 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) + + // 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(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + 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) + + // 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(t, 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) + }) +} + +func TestUserAuthenticationDeleteUser(t *testing.T) { + Convey("Delete User as Admin", t, func() { + f1, db1, c1, err := connect(t, "", "admin", "admin") + 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) + + // 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(t, "", "admin", "admin") + 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) + + // 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(t, "", "admin", "admin") + 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) + + // 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(t, 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(t, "", "admin", "admin") + 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) + + // 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(t, 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) + }) +} + +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() + }) +} 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/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 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) +}