From 5b49d3a340f017820b872ca1aa85bdab6dc7a86e Mon Sep 17 00:00:00 2001 From: GRMrGecko Date: Sun, 13 Aug 2023 19:37:01 -0500 Subject: [PATCH] Add dictionary functions, cleanup documentation, and move sub element processing functions to inernal functions. --- client.go | 14 ++--- client_test.go | 8 +-- errors.go | 2 +- request.go | 6 +- response.go | 145 +++++++++++++++++++++++++++++++------------------ 5 files changed, 107 insertions(+), 68 deletions(-) diff --git a/client.go b/client.go index bc103ab..e1da528 100644 --- a/client.go +++ b/client.go @@ -13,7 +13,7 @@ import ( "github.com/jcmturner/gokrb5/v8/spnego" ) -// Client: The base object for connections to FreeIPA API. +// The base object for connections to FreeIPA API. type Client struct { uriBase string client *http.Client @@ -22,7 +22,7 @@ type Client struct { krb5 *krb5client.Client } -// init: Common init code for each connection type, mainly sets http.Client and uriBase. +// Internal function with common init code for each connection type, mainly sets http.Client and uriBase. func (c *Client) init(host string, transport *http.Transport) error { // Create a cookie jar to store FreeIPA session cookies. jar, err := cookiejar.New(&cookiejar.Options{}) @@ -44,7 +44,7 @@ func (c *Client) init(host string, transport *http.Transport) error { return nil } -// Connect: Make a new client using standard username/password login. +// Make a new client and login using standard username/password. func Connect(host string, transport *http.Transport, user, password string) (*Client, error) { // Make the client config and save credentials. client := &Client{ @@ -67,7 +67,7 @@ func Connect(host string, transport *http.Transport, user, password string) (*Cl return client, nil } -// login: Login using standard credentials. +// Login using standard credentials. func (c *Client) login() error { // If login is called, but kerberos client is configured, use kerberos login instead. // This allows standard re-authentication calls to work with both kerbeos and standard authenciation. @@ -98,7 +98,7 @@ func (c *Client) login() error { return nil } -// KerberosConnectOptions: Options for connecting to Kerberos. +// Options for connecting to Kerberos. type KerberosConnectOptions struct { Krb5ConfigReader io.Reader KeytabReader io.Reader @@ -106,7 +106,7 @@ type KerberosConnectOptions struct { Realm string } -// ConnectWithKerberos: Create a new client using Kerberos authentication. +// Create a new client using Kerberos authentication. func ConnectWithKerberos(host string, transport *http.Transport, options *KerberosConnectOptions) (*Client, error) { // Read the kerberos configuration file for server connection information. krb5Config, err := krb5config.NewFromReader(options.Krb5ConfigReader) @@ -150,7 +150,7 @@ func ConnectWithKerberos(host string, transport *http.Transport, options *Kerber return client, nil } -// loginWithKerberos: Authenticate using kerberos client. +// Login using kerberos client. The regular login function will call this function if needed. func (c *Client) loginWithKerberos() error { // Wrapper for authenticating with Kerberos credentials. spnegoCl := spnego.NewClient(c.krb5, c.client, "") diff --git a/client_test.go b/client_test.go index 1665f9e..208978c 100644 --- a/client_test.go +++ b/client_test.go @@ -15,7 +15,7 @@ import ( // Unused port for testing. const httpsPort = 8831 -// handleLogin: Test login handler. +// Test login handler. func handleLogin(w http.ResponseWriter, req *http.Request) { // Logins are form data posts. req.ParseForm() @@ -53,7 +53,7 @@ func handleLogin(w http.ResponseWriter, req *http.Request) { } } -// sendInvalidJSON: General invalid json error response for testing error handling. +// General invalid json error response for testing error handling. func sendInvalidJSON(w http.ResponseWriter) { f, err := os.Open("test/invalid_json.json") if err != nil { @@ -63,7 +63,7 @@ func sendInvalidJSON(w http.ResponseWriter) { io.Copy(w, f) } -// handleJSON: Handle the json session test request. +// Handle the json session test request. func handleJSON(w http.ResponseWriter, req *http.Request) { // If session cookie doesn't exist, something is wrong. Send unauthenticated response. cookie, err := req.Cookie("ipa_session") @@ -107,7 +107,7 @@ func handleJSON(w http.ResponseWriter, req *http.Request) { } } -// TestLogin: General library tests with test server. +// General library tests with test server. func TestLogin(t *testing.T) { // Spin up test server using port specified above. srvAddr := fmt.Sprintf("127.0.0.1:%d", httpsPort) diff --git a/errors.go b/errors.go index 0c2aebd..897fc66 100644 --- a/errors.go +++ b/errors.go @@ -140,7 +140,7 @@ const ( rejectionReasonHTTPHeader = "X-Ipa-Rejection-Reason" ) -// unauthorizedHTTPError: Add information from the rejection reason header to unauthorized error. +// Add information from the rejection reason header to unauthorized error. func unauthorizedHTTPError(resp *http.Response) error { var errorCode int rejectionReason := resp.Header.Get(rejectionReasonHTTPHeader) diff --git a/request.go b/request.go index e3c805b..cb5deed 100644 --- a/request.go +++ b/request.go @@ -16,7 +16,7 @@ type Request struct { Params []interface{} `json:"params"` } -// NewRequest: Create a new request providing method, args, and parameters. +// Create a new API request. func NewRequest(method string, args []interface{}, parms map[string]interface{}) *Request { // Add API version to the parameters. parms["version"] = apiVersion @@ -34,7 +34,7 @@ func NewRequest(method string, args []interface{}, parms map[string]interface{}) return req } -// Do: Have the client do the request. +// Have the client perform the request. func (c *Client) Do(req *Request) (*Response, error) { // Send request. res, err := c.sendRequest(req) @@ -67,7 +67,7 @@ func (c *Client) Do(req *Request) (*Response, error) { return ParseResponse(res.Body) } -// sendRequest: Encode and send the request to the session. +// Encode and send the request to the session. func (c *Client) sendRequest(request *Request) (*http.Response, error) { // Encode to JSON. data, err := json.Marshal(request) diff --git a/response.go b/response.go index 2bf8c2a..7ed1c66 100644 --- a/response.go +++ b/response.go @@ -12,7 +12,7 @@ import ( // https://github.com/freeipa/freeipa/blob/ipa-4-7/ipalib/constants.py#L271 const LDAPGeneralizedTimeFormat = "20060102150405Z" -// Message: Used in providing extra messages and error response. +// Used in providing extra messages and error response. type Message struct { Type string `json:"type"` Message string `json:"message"` @@ -20,12 +20,12 @@ type Message struct { Name string `json:"name"` } -// string: Convert the message into a combind string. +// Convert the message into a combind string. func (t *Message) string() string { return fmt.Sprintf("%v (%v): %v", t.Name, t.Code, t.Message) } -// Result: Standard result in response from FreeIPA. +// Standard result in response from FreeIPA. type Result struct { Count int `json:"count"` Truncated bool `json:"truncated"` @@ -38,7 +38,7 @@ type Result struct { Value string `json:"value,omitempty"` } -// Response: Standard response from FreeIPA. +// Standard response from FreeIPA. type Response struct { Error *Message `json:"error"` Result *Result `json:"result"` @@ -46,7 +46,7 @@ type Response struct { Principal string `json:"principal"` } -// ParseResponse: Parse response from reader. +// Parse response from reader. func ParseResponse(body io.Reader) (*Response, error) { // Decode JSON response. res := new(Response) @@ -66,7 +66,7 @@ func ParseResponse(body io.Reader) (*Response, error) { return res, nil } -// BoolResult: Decode results which are boolean formatted, usually used to indicate success or state. +// Decode results which are boolean formatted, usually used to indicate success or state. func (r *Response) BoolResult() bool { if r.Result == nil { return false @@ -78,6 +78,7 @@ func (r *Response) BoolResult() bool { return v } +// Count the number of results that this request has. func (r *Response) CountResults() int { if r.Result == nil { return -1 @@ -89,8 +90,8 @@ func (r *Response) CountResults() int { return len(a) } -// GetAtIndex: Get an interface for a key. -func (r *Response) GetAtIndex(index int, key string) ([]interface{}, bool) { +// Return dictionary at index. +func (r *Response) DictAtIndex(index int) (map[string]interface{}, bool) { if r.Result == nil { return nil, false } @@ -104,27 +105,21 @@ func (r *Response) GetAtIndex(index int, key string) ([]interface{}, bool) { } d := a[index] dict, ok := d.(map[string]interface{}) - if !ok { - return nil, false - } - v, ok := dict[key] - if !ok { - return nil, false - } - a, ok = v.([]interface{}) - if !ok { - // Apparently FreeIPA sometimes returns a string outside of an array, so this catches that. - return []interface{}{v}, true - } - return a, true + return dict, ok } -// Get: Get an interface for a key. -func (r *Response) Get(key string) ([]interface{}, bool) { +// Return dictionary. +func (r *Response) Dict() (map[string]interface{}, bool) { if r.Result == nil { return nil, false } dict, ok := r.Result.Result.(map[string]interface{}) + return dict, ok +} + +// Get an interface for a key. +func (r *Response) GetAtIndex(index int, key string) ([]interface{}, bool) { + dict, ok := r.DictAtIndex(index) if !ok { return nil, false } @@ -140,8 +135,52 @@ func (r *Response) Get(key string) ([]interface{}, bool) { return a, true } -// GetBoolProcess: Process bool element. -func (r *Response) GetBoolProcess(v interface{}) (bool, bool) { +// Get an interface for a key. +func (r *Response) Get(key string) ([]interface{}, bool) { + dict, ok := r.Dict() + if !ok { + return nil, false + } + v, ok := dict[key] + if !ok { + return nil, false + } + a, ok := v.([]interface{}) + if !ok { + // Apparently FreeIPA sometimes returns a string outside of an array, so this catches that. + return []interface{}{v}, true + } + return a, true +} + +// Get all keys at index. +func (r *Response) KeysAtIndex(index int) ([]string, bool) { + var res []string + dict, ok := r.DictAtIndex(index) + if !ok { + return res, false + } + for k := range dict { + res = append(res, k) + } + return res, true +} + +// Get all keys. +func (r *Response) Keys() ([]string, bool) { + var res []string + dict, ok := r.Dict() + if !ok { + return res, false + } + for k := range dict { + res = append(res, k) + } + return res, true +} + +// Process sub element with bool. +func (r *Response) processBool(v interface{}) (bool, bool) { a, ok := v.(bool) if !ok { return false, false @@ -149,26 +188,26 @@ func (r *Response) GetBoolProcess(v interface{}) (bool, bool) { return a, true } -// GetBoolAtIndex: Get a boolean from a key at an index. +// Get a boolean from a key at an index. func (r *Response) GetBoolAtIndex(index int, key string) (bool, bool) { v, ok := r.GetAtIndex(index, key) if !ok || len(v) < 1 { return false, false } - return r.GetBoolProcess(v[0]) + return r.processBool(v[0]) } -// GetBool: Get a boolean from a key. +// Get a boolean from a key. func (r *Response) GetBool(key string) (bool, bool) { v, ok := r.Get(key) if !ok || len(v) < 1 { return false, false } - return r.GetBoolProcess(v[0]) + return r.processBool(v[0]) } -// GetStringProcess: Process sub element with string. -func (r *Response) GetStringProcess(v []interface{}) ([]string, bool) { +// Process sub element with string. +func (r *Response) processString(v []interface{}) ([]string, bool) { var res []string for _, p := range v { s, ok := p.(string) @@ -180,25 +219,25 @@ func (r *Response) GetStringProcess(v []interface{}) ([]string, bool) { return res, true } -// GetStringsAtIndex: Get string value for key at an index. +// Get string value for key at an index. func (r *Response) GetStringsAtIndex(index int, key string) ([]string, bool) { v, ok := r.GetAtIndex(index, key) if !ok { return []string{}, false } - return r.GetStringProcess(v) + return r.processString(v) } -// GetStrings: Get string value for key. +// Get string value for key. func (r *Response) GetStrings(key string) ([]string, bool) { v, ok := r.Get(key) if !ok { return []string{}, false } - return r.GetStringProcess(v) + return r.processString(v) } -// GetStringAtIndex: Get string value for key at an index. +// Get string value for key at an index. func (r *Response) GetStringAtIndex(index int, key string) (string, bool) { v, ok := r.GetStringsAtIndex(index, key) if !ok || len(v) < 1 { @@ -207,7 +246,7 @@ func (r *Response) GetStringAtIndex(index int, key string) (string, bool) { return v[0], true } -// GetString: Get string value for key. +// Get string value for key. func (r *Response) GetString(key string) (string, bool) { v, ok := r.GetStrings(key) if !ok || len(v) < 1 { @@ -216,8 +255,8 @@ func (r *Response) GetString(key string) (string, bool) { return v[0], true } -// GetDataProcess: Process a sub element with bytes. -func (r *Response) GetDataProcess(v []interface{}) ([][]byte, bool) { +// Process sub element with bytes. +func (r *Response) processData(v []interface{}) ([][]byte, bool) { var res [][]byte for _, p := range v { var bytes []byte @@ -242,25 +281,25 @@ func (r *Response) GetDataProcess(v []interface{}) ([][]byte, bool) { return res, true } -// GetDatasAtIndex: Get byte array for key at an index. +// Get byte array for key at an index. func (r *Response) GetDatasAtIndex(index int, key string) ([][]byte, bool) { v, ok := r.GetAtIndex(index, key) if !ok { return [][]byte{}, false } - return r.GetDataProcess(v) + return r.processData(v) } -// GetDatas: Get byte array for key. +// Get byte array for key. func (r *Response) GetDatas(key string) ([][]byte, bool) { v, ok := r.Get(key) if !ok { return [][]byte{}, false } - return r.GetDataProcess(v) + return r.processData(v) } -// GetDataAtIndex: Get byte array for key at an index. +// Get byte array for key at an index. func (r *Response) GetDataAtIndex(index int, key string) ([]byte, bool) { v, ok := r.GetDatasAtIndex(index, key) if !ok || len(v) < 1 { @@ -269,7 +308,7 @@ func (r *Response) GetDataAtIndex(index int, key string) ([]byte, bool) { return v[0], true } -// GetData: Get byte array for key. +// Get byte array for key. func (r *Response) GetData(key string) ([]byte, bool) { v, ok := r.GetDatas(key) if !ok || len(v) < 1 { @@ -278,8 +317,8 @@ func (r *Response) GetData(key string) ([]byte, bool) { return v[0], true } -// GetDateTimeProcess: Process a sub element with a date/time value. -func (r *Response) GetDateTimeProcess(v []interface{}) ([]time.Time, bool) { +// Process sub element with a date/time value. +func (r *Response) processDateTime(v []interface{}) ([]time.Time, bool) { var res []time.Time for _, p := range v { dict, ok := p.(map[string]interface{}) @@ -303,25 +342,25 @@ func (r *Response) GetDateTimeProcess(v []interface{}) ([]time.Time, bool) { return res, true } -// GetDateTimesAtIndex: Get date time value for key at an index. +// Get date time value for key at an index. func (r *Response) GetDateTimesAtIndex(index int, key string) ([]time.Time, bool) { v, ok := r.GetAtIndex(index, key) if !ok { return []time.Time{}, false } - return r.GetDateTimeProcess(v) + return r.processDateTime(v) } -// GetDateTimes: Get date time value for key. +// Get date time value for key. func (r *Response) GetDateTimes(key string) ([]time.Time, bool) { v, ok := r.Get(key) if !ok { return []time.Time{}, false } - return r.GetDateTimeProcess(v) + return r.processDateTime(v) } -// GetDateTimeAtIndex: Get date time value for key at an index. +// Get date time value for key at an index. func (r *Response) GetDateTimeAtIndex(index int, key string) (time.Time, bool) { v, ok := r.GetDateTimesAtIndex(index, key) if !ok || len(v) < 1 { @@ -330,7 +369,7 @@ func (r *Response) GetDateTimeAtIndex(index int, key string) (time.Time, bool) { return v[0], true } -// GetDateTime: Get date time value for key. +// Get date time value for key. func (r *Response) GetDateTime(key string) (time.Time, bool) { v, ok := r.GetDateTimes(key) if !ok || len(v) < 1 {