| // Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. |
| // |
| // 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" |
| ) |
| |
| var ( |
| conn *SQLiteConn |
| create func(t *testing.T, username, password string) (file string, err error) |
| createWithCrypt func(t *testing.T, username, password, crypt, salt string) (file string, err error) |
| connect func(t *testing.T, f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) |
| connectWithCrypt func(t *testing.T, f string, username, password string, crypt string, salt 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 |
| }, |
| }) |
| |
| create = func(t *testing.T, username, password string) (file string, err error) { |
| var db *sql.DB |
| file, db, _, err = connect(t, "", username, password) |
| db.Close() |
| return |
| } |
| |
| createWithCrypt = func(t *testing.T, username, password, crypt, salt string) (file string, err error) { |
| var db *sql.DB |
| file, db, _, err = connectWithCrypt(t, "", "admin", "admin", crypt, salt) |
| db.Close() |
| return |
| } |
| |
| 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 |
| } |
| |
| 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 |
| } |
| |
| 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 TestUserAuthCreateDatabase(t *testing.T) { |
| f, db, c, err := connect(t, "", "admin", "admin") |
| if err != nil && c == nil && db == nil { |
| t.Fatal(err) |
| } |
| defer db.Close() |
| defer os.Remove(f) |
| |
| enabled, err := authEnabled(db) |
| if err != nil || !enabled { |
| t.Fatalf("UserAuth not enabled: %s", err) |
| } |
| |
| e, err := userExists(db, "admin") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if e != 1 { |
| t.Fatal("UserAuth: admin does not exists") |
| } |
| a, err := isAdmin(db, "admin") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !a { |
| t.Fatal("UserAuth: User is not administrator") |
| } |
| } |
| |
| func TestUserAuthLogin(t *testing.T) { |
| f1, err := create(t, "admin", "admin") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer os.Remove(f1) |
| |
| f2, db2, c2, err := connect(t, f1, "admin", "admin") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer db2.Close() |
| if f1 != f2 { |
| t.Fatal("UserAuth: Database file mismatch") |
| } |
| |
| // Test lower level authentication |
| err = c2.Authenticate("admin", "admin") |
| if err != nil { |
| t.Fatalf("UserAuth: *SQLiteConn.Authenticate() Failed: %s", err) |
| } |
| |
| // Test Login Failed |
| _, _, _, err = connect(t, f1, "admin", "invalid") |
| if err == nil { |
| t.Fatal("Login successful while expecting to fail") |
| } |
| if err != ErrUnauthorized { |
| t.Fatal(err) |
| } |
| err = c2.Authenticate("admin", "invalid") |
| if err == nil { |
| t.Fatal("Login successful while expecting to fail") |
| } |
| if err != ErrUnauthorized { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestUserAuthAddAdmin(t *testing.T) { |
| f, db, c, err := connect(t, "", "admin", "admin") |
| if err != nil && c == nil && db == nil { |
| t.Fatal(err) |
| } |
| defer db.Close() |
| defer os.Remove(f) |
| |
| // Add Admin User through SQL call |
| rv, err := addUser(db, "admin2", "admin2", 1) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| // Check if user was created |
| exists, err := userExists(db, "admin2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 1 { |
| t.Fatal("UserAuth: 'admin2' does not exists") |
| } |
| |
| // Check if user was created as an Administrator |
| admin, err := isAdmin(db, "admin2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !admin { |
| t.Fatal("UserAuth: 'admin2' is not administrator") |
| } |
| |
| // Test *SQLiteConn |
| err = c.AuthUserAdd("admin3", "admin3", true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Check if user was created |
| exists, err = userExists(db, "admin2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 1 { |
| t.Fatal("UserAuth: 'admin3' does not exists") |
| } |
| |
| // Check if the user was created as an Administrator |
| admin, err = isAdmin(db, "admin3") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !admin { |
| t.Fatal("UserAuth: 'admin3' is not administrator") |
| } |
| } |
| |
| func TestUserAuthAddUser(t *testing.T) { |
| f1, db1, c, err := connect(t, "", "admin", "admin") |
| if err != nil && c == nil && db == nil { |
| t.Fatal(err) |
| } |
| defer os.Remove(f1) |
| |
| // Add user through SQL call |
| rv, err := addUser(db1, "user", "user", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| // Check if user was created |
| exists, err := userExists(db1, "user") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 1 { |
| t.Fatal("UserAuth: 'user' does not exists") |
| } |
| |
| // Check if user was created as an Administrator |
| admin, err := isAdmin(db1, "user") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if admin { |
| t.Fatal("UserAuth: 'user' is administrator") |
| } |
| |
| // Test *SQLiteConn |
| err = c.AuthUserAdd("user2", "user2", false) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Check if user was created |
| exists, err = userExists(db1, "user2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 1 { |
| t.Fatal("UserAuth: 'user2' does not exists") |
| } |
| |
| // Check if the user was created as an Administrator |
| admin, err = isAdmin(db1, "user2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if admin { |
| t.Fatal("UserAuth: 'user2' is administrator") |
| } |
| |
| // Reconnect as normal user |
| db1.Close() |
| _, db2, c2, err := connect(t, f1, "user", "user") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer db2.Close() |
| |
| // Try to create admin user while logged in as normal user |
| rv, err = addUser(db2, "admin2", "admin2", 1) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != SQLITE_AUTH { |
| t.Fatal("Created admin user while not allowed") |
| } |
| |
| err = c2.AuthUserAdd("admin3", "admin3", true) |
| if err != ErrAdminRequired { |
| t.Fatal("Created admin user while not allowed") |
| } |
| |
| // Try to create normal user while logged in as normal user |
| rv, err = addUser(db2, "user3", "user3", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != SQLITE_AUTH { |
| t.Fatal("Created user while not allowed") |
| } |
| |
| err = c2.AuthUserAdd("user4", "user4", false) |
| if err != ErrAdminRequired { |
| t.Fatal("Created user while not allowed") |
| } |
| } |
| |
| func TestUserAuthModifyUser(t *testing.T) { |
| f1, db1, c1, err := connect(t, "", "admin", "admin") |
| if err != nil && c1 == nil && db == nil { |
| t.Fatal(err) |
| } |
| defer os.Remove(f1) |
| |
| // Modify Password for current logged in admin |
| // through SQL |
| rv, err := modifyUser(db1, "admin", "admin2", 1) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to modify password for admin") |
| } |
| |
| // Modify password for current logged in admin |
| // through *SQLiteConn |
| err = c1.AuthUserChange("admin", "admin3", true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Modify Administrator Flag |
| // Because we are current logged in as 'admin' |
| // Changing our own admin flag should fail. |
| rv, err = modifyUser(db1, "admin", "admin3", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != SQLITE_AUTH { |
| t.Fatal("Successfully changed admin flag while not allowed") |
| } |
| |
| // Modify admin flag through (*SQLiteConn) |
| // Because we are current logged in as 'admin' |
| // Changing our own admin flag should fail. |
| err = c1.AuthUserChange("admin", "admin3", false) |
| if err != ErrAdminRequired { |
| t.Fatal("Successfully changed admin flag while not allowed") |
| } |
| |
| // Add normal user |
| rv, err = addUser(db1, "user", "password", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| rv, err = addUser(db1, "user2", "user2", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| // Modify other user password and flag through SQL |
| rv, err = modifyUser(db1, "user", "pass", 1) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to modify password for user") |
| } |
| |
| // Modify other user password and flag through *SQLiteConn |
| err = c1.AuthUserChange("user", "newpass", false) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Disconnect database for reconnect |
| db1.Close() |
| _, db2, c2, err := connect(t, f1, "user", "newpass") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer db2.Close() |
| |
| // Modify other user password through SQL |
| rv, err = modifyUser(db2, "user2", "newpass", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != SQLITE_AUTH { |
| t.Fatal("Password change successful while not allowed") |
| } |
| |
| // Modify other user password and flag through *SQLiteConn |
| err = c2.AuthUserChange("user2", "invalid", false) |
| if err != ErrAdminRequired { |
| t.Fatal("Password change successful while not allowed") |
| } |
| } |
| |
| func TestUserAuthDeleteUser(t *testing.T) { |
| f1, db1, c, err := connect(t, "", "admin", "admin") |
| if err != nil && c == nil && db == nil { |
| t.Fatal(err) |
| } |
| defer os.Remove(f1) |
| |
| // Add Admin User 2 |
| rv, err := addUser(db1, "admin2", "admin2", 1) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| rv, err = addUser(db1, "admin3", "admin3", 1) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| // Check if user was created |
| exists, err := userExists(db1, "admin2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 1 { |
| t.Fatal("UserAuth: 'admin2' does not exists") |
| } |
| |
| exists, err = userExists(db1, "admin3") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 1 { |
| t.Fatal("UserAuth: 'admin2' does not exists") |
| } |
| |
| // Delete user through SQL |
| rv, err = deleteUser(db1, "admin2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to delete admin2") |
| } |
| |
| // Verify user admin2 deleted |
| exists, err = userExists(db1, "admin2") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 0 { |
| t.Fatal("UserAuth: 'admin2' still exists") |
| } |
| |
| // Delete user through *SQLiteConn |
| rv, err = deleteUser(db1, "admin3") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to delete admin3") |
| } |
| |
| // Verify user admin3 deleted |
| exists, err = userExists(db1, "admin3") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exists != 0 { |
| t.Fatal("UserAuth: 'admin3' still exists") |
| } |
| |
| // Add normal user for reconnect and privileges check |
| rv, err = addUser(db1, "reconnect", "reconnect", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| // Add normal user for deletion through SQL |
| rv, err = addUser(db1, "user", "user", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| rv, err = addUser(db1, "user2", "user2", 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != 0 { |
| t.Fatal("Failed to add user") |
| } |
| |
| // Close database for reconnect |
| db1.Close() |
| |
| // Reconnect as normal user |
| _, db2, c2, err := connect(t, f1, "reconnect", "reconnect") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer db2.Close() |
| |
| // Delete user while logged in as normal user |
| // through SQL |
| rv, err = deleteUser(db2, "user") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if rv != SQLITE_AUTH { |
| t.Fatal("Successfully deleted user wthout proper privileges") |
| } |
| |
| // Delete user while logged in as normal user |
| // through *SQLiteConn |
| err = c2.AuthUserDelete("user2") |
| if err != ErrAdminRequired { |
| t.Fatal("Successfully deleted user wthout proper privileges") |
| } |
| } |
| |
| func TestUserAuthEncoders(t *testing.T) { |
| cases := map[string]string{ |
| "sha1": "", |
| "ssha1": "salted", |
| "sha256": "", |
| "ssha256": "salted", |
| "sha384": "", |
| "ssha384": "salted", |
| "sha512": "", |
| "ssha512": "salted", |
| } |
| |
| for enc, salt := range cases { |
| f, err := createWithCrypt(t, "admin", "admin", enc, salt) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer os.Remove(f) |
| |
| _, db, _, err := connectWithCrypt(t, f, "admin", "admin", enc, salt) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer db.Close() |
| if e, err := authEnabled(db); err != nil && !e { |
| t.Fatalf("UserAuth (%s) not enabled %s", enc, err) |
| } |
| } |
| } |