diff --git a/Makefile b/Makefile index 6212e4e..4743716 100644 --- a/Makefile +++ b/Makefile @@ -14,3 +14,6 @@ lint: test: lint go test -count=1 -v -cover --race -tags="unittests" ./ + +test_specific: lint + go test -count=1 -v -cover --race -tags="specific" ./ \ No newline at end of file diff --git a/client.go b/client.go index 0fbb373..a52702e 100644 --- a/client.go +++ b/client.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "net/http" "net/url" @@ -24,6 +25,9 @@ type Config struct { Username string DisableTLSVerification bool DisableCookies bool + Token string + DataSource string + Cookies map[string]string } // Client - base client for guacamole interactions @@ -54,31 +58,56 @@ func New(config Config) Client { // Connect - function for establishing connection to guacamole func (c *Client) Connect() error { - resp, err := c.client.PostForm(fmt.Sprintf("%s/%s", c.config.URL, tokenPath), - url.Values{ - "username": {c.config.Username}, - "password": {c.config.Password}, - }) - if err != nil { - return err - } - if resp.StatusCode == 403 { - return fmt.Errorf("Invalid Credentials") - } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - var tokenresp types.AuthenticationResponse + // check if token and dataSource are provided + if c.config.Token != "" && c.config.DataSource != "" { + // test supplied token and dataSource are valid + c.baseURL = fmt.Sprintf("%s/api/session/data/%s", c.config.URL, c.config.DataSource) + req, _ := c.CreateJSONRequest("GET", fmt.Sprintf("%s/schema/userAttributes", c.baseURL), nil) + + c.token = c.config.Token + for k, v := range c.config.Cookies { + cookie := &http.Cookie{ + Name: k, + Value: v, + } + c.cookies = append(c.cookies, cookie) + } + var result interface{} + err := c.Call(req, &result) + if err != nil { + log.Printf("%s", err) + return err + } + if result == nil { + return fmt.Errorf("unable to connect using supplied token and dataSource") + } + } else { + resp, err := c.client.PostForm(fmt.Sprintf("%s/%s", c.config.URL, tokenPath), + url.Values{ + "username": {c.config.Username}, + "password": {c.config.Password}, + }) + if err != nil { + return err + } + if resp.StatusCode == 403 { + return fmt.Errorf("invalid Credentials") + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + var tokenresp types.AuthenticationResponse - err = json.Unmarshal(body, &tokenresp) - if err != nil { - return err - } - c.token = tokenresp.AuthToken - c.baseURL = fmt.Sprintf("%s/api/session/data/%s", c.config.URL, tokenresp.DataSource) - if !(c.config.DisableCookies) { - c.cookies = resp.Cookies() + err = json.Unmarshal(body, &tokenresp) + if err != nil { + return err + } + c.token = tokenresp.AuthToken + c.baseURL = fmt.Sprintf("%s/api/session/data/%s", c.config.URL, tokenresp.DataSource) + if !(c.config.DisableCookies) { + c.cookies = resp.Cookies() + } } return nil } @@ -115,11 +144,8 @@ func (c *Client) CreateJSONRequest(method string, path string, params interface{ // Call - function for handling http requests func (c *Client) Call(request *http.Request, result interface{}) error { - // Add authentication token to query params - q := request.URL.Query() - q.Add("token", c.token) - - request.URL.RawQuery = q.Encode() + // Add authentication token to request Header + request.Header.Set("Guacamole-Token", c.token) // Add cookies if configured if !(c.config.DisableCookies) { @@ -141,7 +167,7 @@ func (c *Client) Call(request *http.Request, result interface{}) error { body := io.TeeReader(response.Body, &rawBodyBuffer) var responseBody interface{} json.NewDecoder(body).Decode(&responseBody) - return fmt.Errorf("Request %+v\n failed with status code %d\n response %+v\n%+v", request, + return fmt.Errorf("request %+v\n failed with status code %d\n response %+v\n%+v", request, response.StatusCode, responseBody, response) } diff --git a/connection_groups.go b/connection_groups.go index 8a7a041..5c6b064 100644 --- a/connection_groups.go +++ b/connection_groups.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "net/url" - "strings" "github.com/techBeck03/guacamole-api-client/types" ) @@ -30,31 +29,30 @@ func (c *Client) GetConnectionTree(identifier string) (types.GuacConnectionGroup return ret, nil } -// flatten Flattens the -func flatten(nested []types.GuacConnectionGroup) ([]types.GuacConnection, []types.GuacConnectionGroup, error) { - flatConns := []types.GuacConnection{} - flatGrps := []types.GuacConnectionGroup{} - for _, groups := range nested { - flatGrps = append(flatGrps, groups) - if len(groups.ChildGroups) > 0 { - conns, subgrps, err := flatten(groups.ChildGroups) - if err != nil { - return nil, nil, err - } - for _, c := range conns { - flatConns = append(flatConns, c) - } - for _, g := range subgrps { - flatGrps = append(flatGrps, g) - } +// getPathTree generates a map of all connection paths +func (c *Client) getPathTree(nested types.GuacConnectionGroup, results *types.GuacConnectionGroupPathTree) error { + for _, group := range nested.ChildGroups { + if nested.Path != "" { + group.Path = fmt.Sprintf("%s/%s", nested.Path, group.Name) + } else { + group.Path = group.Name } - if len(groups.ChildConnections) > 0 { - for _, c := range groups.ChildConnections { - flatConns = append(flatConns, c) - } + results.Groups[group.Identifier] = group.Path + err := c.getPathTree(group, results) + if err != nil { + return err } } - return flatConns, flatGrps, nil + + for _, connection := range nested.ChildConnections { + if nested.Name == "ROOT" { + results.Connections[connection.Identifier] = connection.Name + } else { + results.Connections[connection.Identifier] = fmt.Sprintf("%s/%s", nested.Path, connection.Name) + } + } + + return nil } // CreateConnectionGroup creates a guacamole connection group @@ -108,46 +106,51 @@ func (c *Client) ReadConnectionGroup(identifier string) (types.GuacConnectionGro // ReadConnectionGroupByPath gets a connection group by path (Parent/Name) func (c *Client) ReadConnectionGroupByPath(path string) (types.GuacConnectionGroup, error) { var ret types.GuacConnectionGroup - var parentIdentifier string - splitPath := strings.Split(path, "/") - groups, err := c.ListConnectionGroups() + groups, err := c.GetConnectionTree("ROOT") if err != nil { return ret, err } - if strings.ToUpper(splitPath[0]) == "ROOT" { - parentIdentifier = "ROOT" - } else { - for group := range groups { - if groups[group].Name == splitPath[0] { - parentIdentifier = groups[group].Identifier - break - } - } - } + var tree types.GuacConnectionGroupPathTree + tree.Connections = make(map[string]string) + tree.Groups = make(map[string]string) + err = c.getPathTree(groups, &tree) - if parentIdentifier == "" { - return ret, fmt.Errorf("No connection group found for parent with name: %s", splitPath[0]) + if err != nil { + return ret, err } - for _, group := range groups { - if (group.ParentIdentifier == parentIdentifier) && (group.Name == splitPath[1]) { - readGroup, err := c.ReadConnectionGroup(group.Identifier) + for i, p := range tree.Groups { + if p == path { + grp, err := c.ReadConnectionGroup(i) + grp.Path = path if err != nil { return ret, err } - ret = readGroup - break + return grp, nil } } - if ret.Identifier == "" { - return ret, fmt.Errorf("No connection group found with parentIdentifier = %s\tname = %s", parentIdentifier, splitPath[1]) + return ret, fmt.Errorf("no connection group found with path: %s", path) +} + +// GetConnectionGroupPathById gets a connection group path by identifier +func (c *Client) GetConnectionGroupPathById(identifier string) (string, error) { + groups, err := c.GetConnectionTree("ROOT") + if err != nil { + return "", err } - return ret, nil + var tree types.GuacConnectionGroupPathTree + tree.Connections = make(map[string]string) + tree.Groups = make(map[string]string) + err = c.getPathTree(groups, &tree) + if err != nil { + return "", err + } + return tree.Groups[identifier], nil } // UpdateConnectionGroup updates a connection group by identifier diff --git a/connection_groups_test.go b/connection_groups_test.go index 23e6c4b..9d93a23 100644 --- a/connection_groups_test.go +++ b/connection_groups_test.go @@ -1,3 +1,4 @@ +//go:build all || unittests // +build all unittests package guacamole @@ -5,6 +6,7 @@ package guacamole import ( "fmt" "os" + "strings" "testing" "github.com/techBeck03/guacamole-api-client/types" @@ -15,16 +17,24 @@ var ( URL: os.Getenv("GUACAMOLE_URL"), Username: os.Getenv("GUACAMOLE_USERNAME"), Password: os.Getenv("GUACAMOLE_PASSWORD"), + Token: os.Getenv("GUACAMOLE_TOKEN"), + DataSource: os.Getenv("GUACAMOLE_DATA_SOURCE"), DisableTLSVerification: true, } testConnectionGroup = types.GuacConnectionGroup{ - Name: "Testing Group", - ParentIdentifier: "ROOT", - Type: "ORGANIZATIONAL", + Name: "Testing Group", + Type: "ORGANIZATIONAL", } ) func TestListConnectionGroups(t *testing.T) { + if os.Getenv("GUACAMOLE_COOKIES") != "" { + connectionGroupsConfig.Cookies = make(map[string]string) + for _, e := range strings.Split(os.Getenv("GUACAMOLE_COOKIES"), ",") { + cookie_split := strings.Split(e, "=") + connectionGroupsConfig.Cookies[cookie_split[0]] = cookie_split[1] + } + } client := New(connectionGroupsConfig) err := client.Connect() @@ -36,12 +46,6 @@ func TestListConnectionGroups(t *testing.T) { if err != nil { t.Errorf("Error %s listing connection group with client %+v", err, client) } - - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestCreateConnectionGroup(t *testing.T) { @@ -52,16 +56,19 @@ func TestCreateConnectionGroup(t *testing.T) { t.Errorf("Error %s connecting to guacamole with config %+v", err, connectionGroupsConfig) } - err = client.CreateConnectionGroup(&testConnectionGroup) + grp, err := client.ReadConnectionGroupByPath(os.Getenv("GUACAMOLE_CONNECTION_PATH")) if err != nil { - t.Errorf("Error %s creating connection group: %s with client %+v", err, testConnectionGroup.Name, client) + t.Errorf("Error unable to find parent group with path: %s", os.Getenv("GUACAMOLE_CONNECTION_PATH")) } - err = client.Disconnect() + testConnectionGroup.ParentIdentifier = grp.Identifier + testConnectionGroup.Path = fmt.Sprintf("%s/%s", grp.Path, testConnectionGroup.Name) + err = client.CreateConnectionGroup(&testConnectionGroup) if err != nil { - t.Errorf("Disconnect errors: %s\n", err) + t.Errorf("Error %s creating connection group: %s with client %+v", err, testConnectionGroup.Name, client) } + } func TestReadConnectionGroup(t *testing.T) { @@ -80,12 +87,6 @@ func TestReadConnectionGroup(t *testing.T) { if connectionGroup.Name != testConnectionGroup.Name { t.Errorf("Expected connection name = %s read connection name = %s", testConnectionGroup.Name, connectionGroup.Name) } - - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestReadConnectionGroupByPath(t *testing.T) { @@ -96,20 +97,11 @@ func TestReadConnectionGroupByPath(t *testing.T) { t.Errorf("Error %s connecting to guacamole with config %+v", err, connectionGroupsConfig) } - connectionGroup, err := client.ReadConnectionGroupByPath(fmt.Sprintf("%s/%s", testConnectionGroup.ParentIdentifier, testConnectionGroup.Name)) + _, err = client.ReadConnectionGroupByPath(testConnectionGroup.Path) if err != nil { - t.Errorf("Error %s reading connection by path: %s with client %+v", err, testConnectionGroup.Name, client) + t.Errorf("Error %s reading connection by path: %s with client %+v", err, testConnectionGroup.Path, client) } - if connectionGroup.Name != testConnectionGroup.Name { - t.Errorf("Expected connection group name = %s read connection group name = %s", fmt.Sprintf("%s/%s", testConnectionGroup.ParentIdentifier, testConnectionGroup.Name), connectionGroup.Name) - } - - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestUpdateConnectionGroup(t *testing.T) { @@ -132,12 +124,6 @@ func TestUpdateConnectionGroup(t *testing.T) { if err != nil { t.Errorf("Error %s updating connection group: %s with client %+v", err, testConnectionGroup.Identifier, client) } - - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestDeleteConnectionGroup(t *testing.T) { @@ -152,10 +138,4 @@ func TestDeleteConnectionGroup(t *testing.T) { if err != nil { t.Errorf("Error %s deleting connection group: %s with client %+v", err, testConnectionGroup.Identifier, client) } - - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } diff --git a/connection_test.go b/connection_test.go index 5ca1482..823d273 100644 --- a/connection_test.go +++ b/connection_test.go @@ -1,10 +1,12 @@ -// +build all unittests +//go:build all || unittests || specific +// +build all unittests specific package guacamole import ( "fmt" "os" + "strings" "testing" "github.com/techBeck03/guacamole-api-client/types" @@ -15,16 +17,26 @@ var ( URL: os.Getenv("GUACAMOLE_URL"), Username: os.Getenv("GUACAMOLE_USERNAME"), Password: os.Getenv("GUACAMOLE_PASSWORD"), + Token: os.Getenv("GUACAMOLE_TOKEN"), + DataSource: os.Getenv("GUACAMOLE_DATA_SOURCE"), DisableTLSVerification: true, } testConnection = types.GuacConnection{ - Name: "Testing Connection", + Name: "Test Connection", Protocol: "ssh", - ParentIdentifier: "ROOT", + ParentIdentifier: "1592", + Path: fmt.Sprintf("%s/Test Connection", os.Getenv("GUACAMOLE_CONNECTION_PATH")), } ) func TestListConnections(t *testing.T) { + if os.Getenv("GUACAMOLE_COOKIES") != "" { + connectionsConfig.Cookies = make(map[string]string) + for _, e := range strings.Split(os.Getenv("GUACAMOLE_COOKIES"), ",") { + cookie_split := strings.Split(e, "=") + connectionsConfig.Cookies[cookie_split[0]] = cookie_split[1] + } + } client := New(connectionsConfig) err := client.Connect() @@ -36,12 +48,6 @@ func TestListConnections(t *testing.T) { if err != nil { t.Errorf("Error %s listing connections with client %+v", err, client) } - - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestCreateConnection(t *testing.T) { @@ -57,11 +63,6 @@ func TestCreateConnection(t *testing.T) { t.Errorf("Error %s creating connection: %s with client %+v", err, testConnection.Name, client) } - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestReadConnection(t *testing.T) { @@ -81,11 +82,6 @@ func TestReadConnection(t *testing.T) { t.Errorf("Expected connection name = %s read connection name = %s", testConnection.Name, connection.Name) } - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestReadConnectionByPath(t *testing.T) { @@ -96,20 +92,11 @@ func TestReadConnectionByPath(t *testing.T) { t.Errorf("Error %s connecting to guacamole with config %+v", err, connectionsConfig) } - connection, err := client.ReadConnectionByPath(fmt.Sprintf("%s/%s", testConnection.ParentIdentifier, testConnection.Name)) + _, err = client.ReadConnectionByPath(fmt.Sprintf("%s", testConnection.Path)) if err != nil { - t.Errorf("Error %s reading connection by path: %s with client %+v", err, testConnection.Name, client) + t.Errorf("Error %s reading connection by path: %s with client %+v", err, testConnection.Path, client) } - if connection.Name != testConnection.Name { - t.Errorf("Expected connection name = %s read connection name = %s", fmt.Sprintf("%s/%s", testConnection.ParentIdentifier, testConnection.Name), connection.Name) - } - - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestUpdateConnection(t *testing.T) { @@ -135,11 +122,6 @@ func TestUpdateConnection(t *testing.T) { t.Errorf("Error %s updating connection: %s with client %+v", err, testConnection.Identifier, client) } - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } func TestDeleteConnection(t *testing.T) { @@ -155,9 +137,4 @@ func TestDeleteConnection(t *testing.T) { t.Errorf("Error %s deleting connection: %s with client %+v", err, testConnection.Identifier, client) } - err = client.Disconnect() - - if err != nil { - t.Errorf("Disconnect errors: %s\n", err) - } } diff --git a/connections.go b/connections.go index 0f7f40e..af49424 100644 --- a/connections.go +++ b/connections.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" "net/url" - "strings" "github.com/techBeck03/guacamole-api-client/types" ) @@ -67,51 +66,51 @@ func (c *Client) ReadConnection(identifier string) (types.GuacConnection, error) // ReadConnectionByPath gets a connection by path (Parent/Name) func (c *Client) ReadConnectionByPath(path string) (types.GuacConnection, error) { var ret types.GuacConnection - var parentIdentifier string - splitPath := strings.Split(path, "/") - groups, err := c.ListConnectionGroups() + groups, err := c.GetConnectionTree("ROOT") if err != nil { return ret, err } - if strings.ToUpper(splitPath[0]) == "ROOT" { - parentIdentifier = "ROOT" - } else { - for _, group := range groups { - if group.Name == splitPath[0] { - parentIdentifier = group.Identifier - break - } - } - } - - if parentIdentifier == "" { - return ret, fmt.Errorf("No connection group found for parent with name: %s", splitPath[0]) - } - - connections, err := c.ListConnections() + var tree types.GuacConnectionGroupPathTree + tree.Connections = make(map[string]string) + tree.Groups = make(map[string]string) + err = c.getPathTree(groups, &tree) if err != nil { return ret, err } - for _, connection := range connections { - if (connection.ParentIdentifier == parentIdentifier) && (connection.Name == splitPath[1]) { - ret, err = c.ReadConnection(connection.Identifier) + for i, p := range tree.Connections { + if p == path { + conn, err := c.ReadConnection(i) + conn.Path = path if err != nil { return ret, err } - break + return conn, nil } } - if ret.Identifier == "" { - return ret, fmt.Errorf("No connection group found with parentIdentifier = %s\tname = %s", parentIdentifier, splitPath[1]) + return ret, fmt.Errorf("no connection found with path: %s", path) +} + +// GetConnectionPathById gets a connection group path by identifier +func (c *Client) GetConnectionPathById(identifier string) (string, error) { + groups, err := c.GetConnectionTree("ROOT") + if err != nil { + return "", err } - return ret, nil + var tree types.GuacConnectionGroupPathTree + tree.Connections = make(map[string]string) + tree.Groups = make(map[string]string) + err = c.getPathTree(groups, &tree) + if err != nil { + return "", err + } + return tree.Connections[identifier], nil } // UpdateConnection updates a connection by identifier diff --git a/formatting.go b/formatting.go new file mode 100644 index 0000000..2a93d67 --- /dev/null +++ b/formatting.go @@ -0,0 +1,11 @@ +package guacamole + +import ( + "encoding/json" + "log" +) + +func prettyPrint(object interface{}) { + output, _ := json.MarshalIndent(object, "", " ") + log.Printf("%s", string(output)) +} diff --git a/types/connection_groups.go b/types/connection_groups.go index f43a1ce..9e3a495 100644 --- a/types/connection_groups.go +++ b/types/connection_groups.go @@ -5,6 +5,7 @@ type GuacConnectionGroup struct { Name string `json:"name"` Identifier string `json:"identifier"` ParentIdentifier string `json:"parentIdentifier"` + Path string `json:"path,omitempty"` Type string `json:"type"` ActiveConnections int `json:"activeConnections"` ChildConnections []GuacConnection `json:"childConnections"` @@ -26,3 +27,9 @@ func (GuacConnectionGroup) ValidTypes() []string { "BALANCING", } } + +// GuacConnectionGroupPathTree returns map of connection paths +type GuacConnectionGroupPathTree struct { + Connections map[string]string + Groups map[string]string +} diff --git a/types/connections.go b/types/connections.go index cccc331..4125740 100644 --- a/types/connections.go +++ b/types/connections.go @@ -5,6 +5,7 @@ type GuacConnection struct { Name string `json:"name"` Identifier string `json:"identifier,omitempty"` ParentIdentifier string `json:"parentIdentifier"` + Path string `json:"path,omitempty"` Protocol string `json:"protocol"` Attributes GuacConnectionAttributes `json:"attributes"` Parameters GuacConnectionParameters `json:"parameters"` diff --git a/user_groups_test.go b/user_groups_test.go index b4d3db6..36c626d 100644 --- a/user_groups_test.go +++ b/user_groups_test.go @@ -1,3 +1,4 @@ +//go:build all || unittests // +build all unittests package guacamole diff --git a/users_test.go b/users_test.go index 764ec3d..0339aa3 100644 --- a/users_test.go +++ b/users_test.go @@ -1,3 +1,4 @@ +//go:build all || unittests // +build all unittests package guacamole