123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- package console
- import (
- "bytes"
- "encoding/json"
- "flag"
- "net/http"
- "os"
- "strconv"
- "strings"
- "sync"
- "testing"
- "devt.de/krotik/common/datautil"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/httputil"
- "devt.de/krotik/common/httputil/access"
- "devt.de/krotik/common/httputil/auth"
- "devt.de/krotik/common/stringutil"
- "devt.de/krotik/eliasdb/api"
- "devt.de/krotik/eliasdb/api/ac"
- v1 "devt.de/krotik/eliasdb/api/v1"
- "devt.de/krotik/eliasdb/config"
- "devt.de/krotik/eliasdb/graph"
- "devt.de/krotik/eliasdb/graph/data"
- "devt.de/krotik/eliasdb/graph/graphstorage"
- )
- const TESTPORT = ":9090"
- var credGiver *CredGiver
- type CredGiver struct {
- UserQueue []string
- PassQueue []string
- }
- func (cg *CredGiver) Reset() {
- cg.UserQueue = nil
- cg.PassQueue = nil
- }
- func (cg *CredGiver) GetCredentials() (string, string) {
- if len(cg.PassQueue) > 0 {
- var u, pw string
- u, cg.UserQueue = cg.UserQueue[0], cg.UserQueue[1:]
- pw, cg.PassQueue = cg.PassQueue[0], cg.PassQueue[1:]
- return u, pw
- }
- return "***user***", "***pass***"
- }
- func ResetDB() {
- mgs := graphstorage.NewMemoryGraphStorage("mystorage")
- gm := graph.NewGraphManager(mgs)
- api.GM = gm
- api.GS = mgs
- }
- func TestMain(m *testing.M) {
- var err error
- flag.Parse()
- // Change ReadLine function
- credGiver = &CredGiver{}
- // Initialise config
- config.LoadDefaultConfig()
- // Initialise DB
- ResetDB()
- // Start the server
- hs, wg := startServer()
- if hs == nil {
- return
- }
- // Disable access logging
- ac.LogAccess = func(v ...interface{}) {}
- // Register public endpoints
- api.RegisterRestEndpoints(api.GeneralEndpointMap)
- api.RegisterRestEndpoints(ac.PublicAccessControlEndpointMap)
- // Initialise auth handler
- ac.AuthHandler = auth.NewCookieAuthHandleFuncWrapper(http.HandleFunc)
- // Important statement! - all registered endpoints afterwards
- // are subject to access control
- api.HandleFunc = ac.AuthHandler.HandleFunc
- // Register management endpoints
- api.RegisterRestEndpoints(ac.AccessManagementEndpointMap)
- api.RegisterRestEndpoints(v1.V1EndpointMap)
- // Initialise user DB
- ac.UserDB, err = datautil.NewEnforcedUserDB("test_user.db", "")
- errorutil.AssertOk(err)
- // Put the UserDB in charge of verifying passwords
- ac.AuthHandler.SetAuthFunc(ac.UserDB.CheckUserPassword)
- // Initialise ACL's
- var conf map[string]interface{}
- errorutil.AssertOk(json.Unmarshal(stringutil.StripCStyleComments(ac.DefaultAccessDB), &conf))
- at, err := access.NewMemoryACLTableFromConfig(conf)
- errorutil.AssertOk(err)
- ac.InitACLs(at)
- // Connect the ACL object to the AuthHandler - this provides authorization for users
- ac.AuthHandler.SetAccessFunc(ac.ACL.CheckHTTPRequest)
- // Adding special handlers which redirect to the login page
- ac.AuthHandler.CallbackSessionExpired = ac.CallbackSessionExpired
- ac.AuthHandler.CallbackUnauthorized = ac.CallbackUnauthorized
- // Add users
- ac.UserDB.UserDB.AddUserEntry("elias", "elias", nil)
- ac.UserDB.UserDB.AddUserEntry("johndoe", "doe", nil)
- // Disable debounce time for unit tests
- ac.DebounceTime = 0
- // Run the tests
- res := m.Run()
- // Stop the server
- stopServer(hs, wg)
- // Stop ACL monitoring
- ac.ACL.Close()
- // Remove files
- os.Remove("test_user.db")
- os.Exit(res)
- }
- /*
- Start a HTTP test server.
- */
- func startServer() (*httputil.HTTPServer, *sync.WaitGroup) {
- hs := &httputil.HTTPServer{}
- var wg sync.WaitGroup
- wg.Add(1)
- go hs.RunHTTPServer(TESTPORT, &wg)
- wg.Wait()
- // Server is started
- if hs.LastError != nil {
- panic(hs.LastError)
- }
- return hs, &wg
- }
- /*
- Stop a started HTTP test server.
- */
- func stopServer(hs *httputil.HTTPServer, wg *sync.WaitGroup) {
- if hs.Running == true {
- wg.Add(1)
- // Server is shut down
- hs.Shutdown()
- wg.Wait()
- } else {
- panic("Server was not running as expected")
- }
- }
- func createSongGraph() {
- constructEdge := func(key string, node1 data.Node, node2 data.Node, number int) data.Edge {
- edge := data.NewGraphEdge()
- edge.SetAttr("key", key)
- edge.SetAttr("kind", "Wrote")
- edge.SetAttr(data.EdgeEnd1Key, node1.Key())
- edge.SetAttr(data.EdgeEnd1Kind, node1.Kind())
- edge.SetAttr(data.EdgeEnd1Role, "Author")
- edge.SetAttr(data.EdgeEnd1Cascading, true)
- edge.SetAttr(data.EdgeEnd2Key, node2.Key())
- edge.SetAttr(data.EdgeEnd2Kind, node2.Kind())
- edge.SetAttr(data.EdgeEnd2Role, "Song")
- edge.SetAttr(data.EdgeEnd2Cascading, false)
- edge.SetAttr("number", number)
- return edge
- }
- storeSong := func(node data.Node, name string, ranking int, number int) {
- node3 := data.NewGraphNode()
- node3.SetAttr("key", name)
- node3.SetAttr("kind", "Song")
- node3.SetAttr("name", name)
- node3.SetAttr("ranking", ranking)
- api.GM.StoreNode("main", node3)
- api.GM.StoreEdge("main", constructEdge(name, node, node3, number))
- }
- node0 := data.NewGraphNode()
- node0.SetAttr("key", "000")
- node0.SetAttr("kind", "Author")
- node0.SetAttr("name", "John")
- node0.SetAttr("desc", "A lonely artisT")
- api.GM.StoreNode("main", node0)
- storeSong(node0, "Aria1", 8, 1)
- storeSong(node0, "Aria2", 2, 2)
- storeSong(node0, "Aria3", 4, 3)
- storeSong(node0, "Aria4", 18, 4)
- node1 := data.NewGraphNode()
- node1.SetAttr("key", "123")
- node1.SetAttr("kind", "Author")
- node1.SetAttr("name", "Mike")
- node1.SetAttr("desc", "An annoying artist")
- api.GM.StoreNode("main", node1)
- storeSong(node1, "LoveSong3", 1, 3)
- storeSong(node1, "FightSong4", 3, 4)
- storeSong(node1, "DeadSong2", 6, 2)
- storeSong(node1, "StrangeSong1", 5, 1)
- node2 := data.NewGraphNode()
- node2.SetAttr("key", "456")
- node2.SetAttr("kind", "Writer")
- node2.SetAttr("name", "Hans")
- node2.SetAttr("text", "A song writer for an artist")
- api.GM.StoreNode("main", node2)
- storeSong(node2, "MyOnlySong3", 19, 3)
- node3 := data.NewGraphNode()
- node3.SetAttr("key", "123")
- node3.SetAttr("kind", "Producer")
- node3.SetAttr("name", "Jack")
- node3.SetAttr("occupation", "A producer of an aRtIsT")
- api.GM.StoreNode("second", node3)
- // Create lots of spam nodes
- for i := 0; i < 21; i++ {
- nodespam := data.NewGraphNode()
- nodespam.SetAttr("key", "000"+strconv.Itoa(i))
- nodespam.SetAttr("kind", "Spam")
- nodespam.SetAttr("name", "Spam"+strconv.Itoa(i))
- api.GM.StoreNode("main", nodespam)
- }
- }
- func TestDescriptions(t *testing.T) {
- var out bytes.Buffer
- ResetDB()
- // Enable access control
- config.Config[config.EnableAccessControl] = true
- defer func() {
- config.Config[config.EnableAccessControl] = false
- }()
- c := NewConsole("http://localhost"+TESTPORT, &out, credGiver.GetCredentials,
- func() string { return "***pass***" },
- func(args []string, e *bytes.Buffer) error {
- return nil
- })
- for _, cmd := range c.Commands() {
- if ok, err := c.Run("help " + cmd.Name()); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- }
- if res := out.String(); res != `
- Exports the data which is currently in the export buffer. The export buffer is filled with the previous command output in a machine readable form.
- Do a full-text search of the database.
- Grants a new permission to a group. Specify first the permission in CRUD format (Create, Read, Update or Delete), then a resource path and then a group name.
- Adds a group to the system.
- Removes a group from the system.
- Returns a list of all groups and their permissions.
- Display descriptions for all available commands.
- Returns general database information such as known node kinds, known attributes, etc ...
- Joins a user to a group.
- Removes a user from a group.
- Log in as a user.
- Log out the current user.
- Changes the password of a user.
- Displays or sets the current partition.
- Revokes permissions to a resource for a group.
- Adds a user to the system.
- Removes a user from the system.
- Returns a table of all users and their groups.
- Displays server version information.
- Returns the current login status.
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("help foo"); ok || err == nil || err.Error() != "Unknown command: foo" {
- t.Error(ok, err)
- return
- }
- }
- func TestNoAuthentication(t *testing.T) {
- var out bytes.Buffer
- var export bytes.Buffer
- ResetDB()
- credGiver.Reset()
- c := NewConsole("http://localhost"+TESTPORT, &out, credGiver.GetCredentials,
- func() string { return "***pass***" },
- func(args []string, e *bytes.Buffer) error {
- export = *e
- return nil
- })
- // Disable authentication
- auth.TestCookieAuthDisabled = true
- defer func() {
- auth.TestCookieAuthDisabled = false
- }()
- // Check we don't have access control commands
- if ok, err := c.Run("whoami"); ok || err == nil || err.Error() != "Unknown command" {
- t.Error(ok, err)
- return
- }
- out.Reset()
- if ok, err := c.Run("ver"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- Connected to: http://localhost:9090
- EliasDB `[1:]+config.ProductVersion+` (REST versions: [v1])
- ` {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("info"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- ┌─────┬──────┐
- │Kind │Count │
- └─────┴──────┘
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- createSongGraph()
- if ok, err := c.Run("info"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- ┌─────────┬───────────┐
- │Kind │Count │
- ├─────────┼───────────┤
- │Author │ 2 │
- │Producer │ 1 │
- │Song │ 9 │
- │Spam │ 21 │
- │Writer │ 1 │
- └─────────┴───────────┘
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("export"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := export.String(); res != `
- Kind, Count
- Author, 2
- Producer, 1
- Song, 9
- Spam, 21
- Writer, 1
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- if ok, err := c.Run("help"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- Command Description
- export Exports the last output.
- find Do a full-text search of the database.
- help Display descriptions for all available commands.
- info Returns general database information.
- part Displays or sets the current partition.
- ver Displays server version information.
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("?"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- Command Description
- export Exports the last output.
- find Do a full-text search of the database.
- help Display descriptions for all available commands.
- info Returns general database information.
- part Displays or sets the current partition.
- ver Displays server version information.
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- }
- func TestBasicCommands(t *testing.T) {
- var out bytes.Buffer
- ResetDB()
- credGiver.Reset()
- // Enable access control
- config.Config[config.EnableAccessControl] = true
- defer func() {
- config.Config[config.EnableAccessControl] = false
- }()
- c := NewConsole("http://localhost"+TESTPORT, &out, credGiver.GetCredentials,
- func() string { return "***pass***" },
- func(args []string, e *bytes.Buffer) error {
- return nil
- })
- // Special command - this should not require a login and should return nobody
- if ok, err := c.Run("whoami"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if strings.TrimSpace(out.String()) != "Nobody - not logged in" {
- t.Error("Unexpected result:", out.String())
- return
- }
- // Now force the login - we should get one failed login
- out.Reset()
- credGiver.UserQueue = []string{"elias", "elias"}
- credGiver.PassQueue = []string{"elia", "elias"}
- if ok, err := c.Run("users"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- Login failed for user elias: Unauthorized (error=<nil>)
- Login as user elias
- ┌─────────┬─────────────┐
- │Username │Groups │
- ├─────────┼─────────────┤
- │elias │admin/public │
- │johndoe │public │
- └─────────┴─────────────┘
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("help"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- Command Description
- export Exports the last output.
- find Do a full-text search of the database.
- grantperm Grants a new permission to a group.
- groupadd Adds a group to the system.
- groupdel Removes a group from the system.
- groups Returns a list of all groups and their permissions.
- help Display descriptions for all available commands.
- info Returns general database information.
- joingroup Joins a user to a group.
- leavegroup Removes a user from a group.
- login Log in as a user.
- logout Log out the current user.
- newpass Changes the password of a user.
- part Displays or sets the current partition.
- revokeperm Revokes permissions to a resource for a group.
- useradd Adds a user to the system.
- userdel Removes a user from the system.
- users Returns a list of all users.
- ver Displays server version information.
- whoami Returns the current login status.
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- // Test log out
- out.Reset()
- if ok, err := c.Run("logout"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `Current user logged out.
- ` {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("whoami"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `Nobody - not logged in
- ` {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- credGiver.UserQueue = []string{"elias"}
- credGiver.PassQueue = []string{"elias"}
- if ok, err := c.Run("login"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `Login as user elias
- ` {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("users"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- ┌─────────┬─────────────┐
- │Username │Groups │
- ├─────────┼─────────────┤
- │elias │admin/public │
- │johndoe │public │
- └─────────┴─────────────┘
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("whoami"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `
- elias
- `[1:] {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("logout"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `Current user logged out.
- ` {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- credGiver.UserQueue = []string{""}
- credGiver.PassQueue = []string{""}
- if ok, err := c.Run("login"); !ok || err != nil {
- t.Error(ok, err)
- return
- }
- if res := out.String(); res != `Skipping authentication
- ` {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- if ok, err := c.Run("users"); ok || err == nil || err.Error() != "GET request to /db/user/u/ failed: Valid credentials required" {
- t.Error("Unexpected result:", ok, err)
- return
- }
- if res := out.String(); res != "" {
- t.Error("Unexpected result:", res)
- return
- }
- out.Reset()
- }
|