Add dictionary functions, cleanup documentation, and move sub element processing functions to inernal functions.

This commit is contained in:
GRMrGecko 2023-08-13 19:37:01 -05:00
parent cb4d9a5c2e
commit 5b49d3a340
5 changed files with 107 additions and 68 deletions

View File

@ -13,7 +13,7 @@ import (
"github.com/jcmturner/gokrb5/v8/spnego" "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 { type Client struct {
uriBase string uriBase string
client *http.Client client *http.Client
@ -22,7 +22,7 @@ type Client struct {
krb5 *krb5client.Client 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 { func (c *Client) init(host string, transport *http.Transport) error {
// Create a cookie jar to store FreeIPA session cookies. // Create a cookie jar to store FreeIPA session cookies.
jar, err := cookiejar.New(&cookiejar.Options{}) jar, err := cookiejar.New(&cookiejar.Options{})
@ -44,7 +44,7 @@ func (c *Client) init(host string, transport *http.Transport) error {
return nil 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) { func Connect(host string, transport *http.Transport, user, password string) (*Client, error) {
// Make the client config and save credentials. // Make the client config and save credentials.
client := &Client{ client := &Client{
@ -67,7 +67,7 @@ func Connect(host string, transport *http.Transport, user, password string) (*Cl
return client, nil return client, nil
} }
// login: Login using standard credentials. // Login using standard credentials.
func (c *Client) login() error { func (c *Client) login() error {
// If login is called, but kerberos client is configured, use kerberos login instead. // 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. // 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 return nil
} }
// KerberosConnectOptions: Options for connecting to Kerberos. // Options for connecting to Kerberos.
type KerberosConnectOptions struct { type KerberosConnectOptions struct {
Krb5ConfigReader io.Reader Krb5ConfigReader io.Reader
KeytabReader io.Reader KeytabReader io.Reader
@ -106,7 +106,7 @@ type KerberosConnectOptions struct {
Realm string 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) { func ConnectWithKerberos(host string, transport *http.Transport, options *KerberosConnectOptions) (*Client, error) {
// Read the kerberos configuration file for server connection information. // Read the kerberos configuration file for server connection information.
krb5Config, err := krb5config.NewFromReader(options.Krb5ConfigReader) krb5Config, err := krb5config.NewFromReader(options.Krb5ConfigReader)
@ -150,7 +150,7 @@ func ConnectWithKerberos(host string, transport *http.Transport, options *Kerber
return client, nil 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 { func (c *Client) loginWithKerberos() error {
// Wrapper for authenticating with Kerberos credentials. // Wrapper for authenticating with Kerberos credentials.
spnegoCl := spnego.NewClient(c.krb5, c.client, "") spnegoCl := spnego.NewClient(c.krb5, c.client, "")

View File

@ -15,7 +15,7 @@ import (
// Unused port for testing. // Unused port for testing.
const httpsPort = 8831 const httpsPort = 8831
// handleLogin: Test login handler. // Test login handler.
func handleLogin(w http.ResponseWriter, req *http.Request) { func handleLogin(w http.ResponseWriter, req *http.Request) {
// Logins are form data posts. // Logins are form data posts.
req.ParseForm() 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) { func sendInvalidJSON(w http.ResponseWriter) {
f, err := os.Open("test/invalid_json.json") f, err := os.Open("test/invalid_json.json")
if err != nil { if err != nil {
@ -63,7 +63,7 @@ func sendInvalidJSON(w http.ResponseWriter) {
io.Copy(w, f) 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) { func handleJSON(w http.ResponseWriter, req *http.Request) {
// If session cookie doesn't exist, something is wrong. Send unauthenticated response. // If session cookie doesn't exist, something is wrong. Send unauthenticated response.
cookie, err := req.Cookie("ipa_session") 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) { func TestLogin(t *testing.T) {
// Spin up test server using port specified above. // Spin up test server using port specified above.
srvAddr := fmt.Sprintf("127.0.0.1:%d", httpsPort) srvAddr := fmt.Sprintf("127.0.0.1:%d", httpsPort)

View File

@ -140,7 +140,7 @@ const (
rejectionReasonHTTPHeader = "X-Ipa-Rejection-Reason" 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 { func unauthorizedHTTPError(resp *http.Response) error {
var errorCode int var errorCode int
rejectionReason := resp.Header.Get(rejectionReasonHTTPHeader) rejectionReason := resp.Header.Get(rejectionReasonHTTPHeader)

View File

@ -16,7 +16,7 @@ type Request struct {
Params []interface{} `json:"params"` 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 { func NewRequest(method string, args []interface{}, parms map[string]interface{}) *Request {
// Add API version to the parameters. // Add API version to the parameters.
parms["version"] = apiVersion parms["version"] = apiVersion
@ -34,7 +34,7 @@ func NewRequest(method string, args []interface{}, parms map[string]interface{})
return req return req
} }
// Do: Have the client do the request. // Have the client perform the request.
func (c *Client) Do(req *Request) (*Response, error) { func (c *Client) Do(req *Request) (*Response, error) {
// Send request. // Send request.
res, err := c.sendRequest(req) res, err := c.sendRequest(req)
@ -67,7 +67,7 @@ func (c *Client) Do(req *Request) (*Response, error) {
return ParseResponse(res.Body) 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) { func (c *Client) sendRequest(request *Request) (*http.Response, error) {
// Encode to JSON. // Encode to JSON.
data, err := json.Marshal(request) data, err := json.Marshal(request)

View File

@ -12,7 +12,7 @@ import (
// https://github.com/freeipa/freeipa/blob/ipa-4-7/ipalib/constants.py#L271 // https://github.com/freeipa/freeipa/blob/ipa-4-7/ipalib/constants.py#L271
const LDAPGeneralizedTimeFormat = "20060102150405Z" const LDAPGeneralizedTimeFormat = "20060102150405Z"
// Message: Used in providing extra messages and error response. // Used in providing extra messages and error response.
type Message struct { type Message struct {
Type string `json:"type"` Type string `json:"type"`
Message string `json:"message"` Message string `json:"message"`
@ -20,12 +20,12 @@ type Message struct {
Name string `json:"name"` Name string `json:"name"`
} }
// string: Convert the message into a combind string. // Convert the message into a combind string.
func (t *Message) string() string { func (t *Message) string() string {
return fmt.Sprintf("%v (%v): %v", t.Name, t.Code, t.Message) 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 { type Result struct {
Count int `json:"count"` Count int `json:"count"`
Truncated bool `json:"truncated"` Truncated bool `json:"truncated"`
@ -38,7 +38,7 @@ type Result struct {
Value string `json:"value,omitempty"` Value string `json:"value,omitempty"`
} }
// Response: Standard response from FreeIPA. // Standard response from FreeIPA.
type Response struct { type Response struct {
Error *Message `json:"error"` Error *Message `json:"error"`
Result *Result `json:"result"` Result *Result `json:"result"`
@ -46,7 +46,7 @@ type Response struct {
Principal string `json:"principal"` Principal string `json:"principal"`
} }
// ParseResponse: Parse response from reader. // Parse response from reader.
func ParseResponse(body io.Reader) (*Response, error) { func ParseResponse(body io.Reader) (*Response, error) {
// Decode JSON response. // Decode JSON response.
res := new(Response) res := new(Response)
@ -66,7 +66,7 @@ func ParseResponse(body io.Reader) (*Response, error) {
return res, nil 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 { func (r *Response) BoolResult() bool {
if r.Result == nil { if r.Result == nil {
return false return false
@ -78,6 +78,7 @@ func (r *Response) BoolResult() bool {
return v return v
} }
// Count the number of results that this request has.
func (r *Response) CountResults() int { func (r *Response) CountResults() int {
if r.Result == nil { if r.Result == nil {
return -1 return -1
@ -89,8 +90,8 @@ func (r *Response) CountResults() int {
return len(a) return len(a)
} }
// GetAtIndex: Get an interface for a key. // Return dictionary at index.
func (r *Response) GetAtIndex(index int, key string) ([]interface{}, bool) { func (r *Response) DictAtIndex(index int) (map[string]interface{}, bool) {
if r.Result == nil { if r.Result == nil {
return nil, false return nil, false
} }
@ -104,27 +105,21 @@ func (r *Response) GetAtIndex(index int, key string) ([]interface{}, bool) {
} }
d := a[index] d := a[index]
dict, ok := d.(map[string]interface{}) dict, ok := d.(map[string]interface{})
if !ok { return dict, 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: Get an interface for a key. // Return dictionary.
func (r *Response) Get(key string) ([]interface{}, bool) { func (r *Response) Dict() (map[string]interface{}, bool) {
if r.Result == nil { if r.Result == nil {
return nil, false return nil, false
} }
dict, ok := r.Result.Result.(map[string]interface{}) 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 { if !ok {
return nil, false return nil, false
} }
@ -140,8 +135,52 @@ func (r *Response) Get(key string) ([]interface{}, bool) {
return a, true return a, true
} }
// GetBoolProcess: Process bool element. // Get an interface for a key.
func (r *Response) GetBoolProcess(v interface{}) (bool, bool) { 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) a, ok := v.(bool)
if !ok { if !ok {
return false, false return false, false
@ -149,26 +188,26 @@ func (r *Response) GetBoolProcess(v interface{}) (bool, bool) {
return a, true 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) { func (r *Response) GetBoolAtIndex(index int, key string) (bool, bool) {
v, ok := r.GetAtIndex(index, key) v, ok := r.GetAtIndex(index, key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {
return false, false 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) { func (r *Response) GetBool(key string) (bool, bool) {
v, ok := r.Get(key) v, ok := r.Get(key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {
return false, false return false, false
} }
return r.GetBoolProcess(v[0]) return r.processBool(v[0])
} }
// GetStringProcess: Process sub element with string. // Process sub element with string.
func (r *Response) GetStringProcess(v []interface{}) ([]string, bool) { func (r *Response) processString(v []interface{}) ([]string, bool) {
var res []string var res []string
for _, p := range v { for _, p := range v {
s, ok := p.(string) s, ok := p.(string)
@ -180,25 +219,25 @@ func (r *Response) GetStringProcess(v []interface{}) ([]string, bool) {
return res, true 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) { func (r *Response) GetStringsAtIndex(index int, key string) ([]string, bool) {
v, ok := r.GetAtIndex(index, key) v, ok := r.GetAtIndex(index, key)
if !ok { if !ok {
return []string{}, false 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) { func (r *Response) GetStrings(key string) ([]string, bool) {
v, ok := r.Get(key) v, ok := r.Get(key)
if !ok { if !ok {
return []string{}, false 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) { func (r *Response) GetStringAtIndex(index int, key string) (string, bool) {
v, ok := r.GetStringsAtIndex(index, key) v, ok := r.GetStringsAtIndex(index, key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {
@ -207,7 +246,7 @@ func (r *Response) GetStringAtIndex(index int, key string) (string, bool) {
return v[0], true return v[0], true
} }
// GetString: Get string value for key. // Get string value for key.
func (r *Response) GetString(key string) (string, bool) { func (r *Response) GetString(key string) (string, bool) {
v, ok := r.GetStrings(key) v, ok := r.GetStrings(key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {
@ -216,8 +255,8 @@ func (r *Response) GetString(key string) (string, bool) {
return v[0], true return v[0], true
} }
// GetDataProcess: Process a sub element with bytes. // Process sub element with bytes.
func (r *Response) GetDataProcess(v []interface{}) ([][]byte, bool) { func (r *Response) processData(v []interface{}) ([][]byte, bool) {
var res [][]byte var res [][]byte
for _, p := range v { for _, p := range v {
var bytes []byte var bytes []byte
@ -242,25 +281,25 @@ func (r *Response) GetDataProcess(v []interface{}) ([][]byte, bool) {
return res, true 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) { func (r *Response) GetDatasAtIndex(index int, key string) ([][]byte, bool) {
v, ok := r.GetAtIndex(index, key) v, ok := r.GetAtIndex(index, key)
if !ok { if !ok {
return [][]byte{}, false 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) { func (r *Response) GetDatas(key string) ([][]byte, bool) {
v, ok := r.Get(key) v, ok := r.Get(key)
if !ok { if !ok {
return [][]byte{}, false 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) { func (r *Response) GetDataAtIndex(index int, key string) ([]byte, bool) {
v, ok := r.GetDatasAtIndex(index, key) v, ok := r.GetDatasAtIndex(index, key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {
@ -269,7 +308,7 @@ func (r *Response) GetDataAtIndex(index int, key string) ([]byte, bool) {
return v[0], true return v[0], true
} }
// GetData: Get byte array for key. // Get byte array for key.
func (r *Response) GetData(key string) ([]byte, bool) { func (r *Response) GetData(key string) ([]byte, bool) {
v, ok := r.GetDatas(key) v, ok := r.GetDatas(key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {
@ -278,8 +317,8 @@ func (r *Response) GetData(key string) ([]byte, bool) {
return v[0], true return v[0], true
} }
// GetDateTimeProcess: Process a sub element with a date/time value. // Process sub element with a date/time value.
func (r *Response) GetDateTimeProcess(v []interface{}) ([]time.Time, bool) { func (r *Response) processDateTime(v []interface{}) ([]time.Time, bool) {
var res []time.Time var res []time.Time
for _, p := range v { for _, p := range v {
dict, ok := p.(map[string]interface{}) dict, ok := p.(map[string]interface{})
@ -303,25 +342,25 @@ func (r *Response) GetDateTimeProcess(v []interface{}) ([]time.Time, bool) {
return res, true 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) { func (r *Response) GetDateTimesAtIndex(index int, key string) ([]time.Time, bool) {
v, ok := r.GetAtIndex(index, key) v, ok := r.GetAtIndex(index, key)
if !ok { if !ok {
return []time.Time{}, false 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) { func (r *Response) GetDateTimes(key string) ([]time.Time, bool) {
v, ok := r.Get(key) v, ok := r.Get(key)
if !ok { if !ok {
return []time.Time{}, false 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) { func (r *Response) GetDateTimeAtIndex(index int, key string) (time.Time, bool) {
v, ok := r.GetDateTimesAtIndex(index, key) v, ok := r.GetDateTimesAtIndex(index, key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {
@ -330,7 +369,7 @@ func (r *Response) GetDateTimeAtIndex(index int, key string) (time.Time, bool) {
return v[0], true 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) { func (r *Response) GetDateTime(key string) (time.Time, bool) {
v, ok := r.GetDateTimes(key) v, ok := r.GetDateTimes(key)
if !ok || len(v) < 1 { if !ok || len(v) < 1 {