From 64aa346e07494980a6c0af21b9a8a0cffa39ba9b Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Wed, 19 Jun 2024 21:22:48 +0100 Subject: [PATCH 1/9] chore: Remove unused helper functions and files in jamfprointegration --- .../{marshalling.go => marshall.go} | 26 +++++- jamfpro/jamfprointegration/urls.go | 2 +- msgraph/msgraphintegration/interface.go | 57 ++++++++++++ msgraph/msgraphintegration/marshall.go | 88 +++++++++++++++++++ msgraph/msgraphintegration/request.go | 23 +++++ .../helpers}/helpers.go | 2 +- 6 files changed, 193 insertions(+), 5 deletions(-) rename jamfpro/jamfprointegration/{marshalling.go => marshall.go} (64%) create mode 100644 msgraph/msgraphintegration/interface.go create mode 100644 msgraph/msgraphintegration/marshall.go create mode 100644 msgraph/msgraphintegration/request.go rename {jamfpro/jamfprointegration => shared/helpers}/helpers.go (96%) diff --git a/jamfpro/jamfprointegration/marshalling.go b/jamfpro/jamfprointegration/marshall.go similarity index 64% rename from jamfpro/jamfprointegration/marshalling.go rename to jamfpro/jamfprointegration/marshall.go index e1aed2d..05c5622 100644 --- a/jamfpro/jamfprointegration/marshalling.go +++ b/jamfpro/jamfprointegration/marshall.go @@ -1,4 +1,4 @@ -// jamfpro_api_request.go +// jamfpro/jamfprointegration/marshall.go package jamfprointegration import ( @@ -11,10 +11,30 @@ import ( "path/filepath" "strings" + "github.com/deploymenttheory/go-api-http-client-integrations/shared/helpers" "go.uber.org/zap" ) -// MarshalRequest encodes the request body according to the endpoint for the API. +// MarshalRequest encodes the request body according to the endpoint for the Jamf Pro API. +// This function marshals the request body as JSON or XML based on the endpoint string. +// It takes an interface{} type body, an HTTP method, and an endpoint as input, and returns the +// marshaled byte slice along with any error encountered during marshaling. +// +// Parameters: +// - body: The request body to be marshaled, of type interface{}. +// - method: The HTTP method being used for the request (e.g., "POST", "PUT", "PATCH"). +// - endpoint: The API endpoint for the request. +// +// Returns: +// - []byte: The marshaled byte slice of the request body. +// - error: Any error encountered during the marshaling process. +// +// Functionality: +// - Determines the format (JSON or XML) based on the endpoint string. +// - Marshals the body as JSON if the endpoint contains "/api" or as XML if it contains "/JSSResource". +// - Logs the marshaled request body for POST, PUT, and PATCH methods using the integrated logger. +// - Logs an error if marshaling fails and returns the error. +// - Returns an error if the format is invalid. func (j *Integration) marshalRequest(body interface{}, method string, endpoint string) ([]byte, error) { var ( data []byte @@ -74,7 +94,7 @@ func (j *Integration) marshalMultipartRequest(fields map[string]string, files ma } for formField, filePath := range files { - file, err := SafeOpenFile(filePath) + file, err := helpers.SafeOpenFile(filePath) if err != nil { j.Logger.Error("Failed to open file securely", zap.String("file", filePath), zap.Error(err)) return nil, "", err diff --git a/jamfpro/jamfprointegration/urls.go b/jamfpro/jamfprointegration/urls.go index a5e75dc..7c2f102 100644 --- a/jamfpro/jamfprointegration/urls.go +++ b/jamfpro/jamfprointegration/urls.go @@ -1,4 +1,4 @@ -// jamfpro_api_url.go +// jamfpro/jamfprointegration/urls.go package jamfprointegration // SetBaseDomain returns the appropriate base domain for URL construction. diff --git a/msgraph/msgraphintegration/interface.go b/msgraph/msgraphintegration/interface.go new file mode 100644 index 0000000..badd89a --- /dev/null +++ b/msgraph/msgraphintegration/interface.go @@ -0,0 +1,57 @@ +// msgraph/msgraphintegration/interface.go +package msgraphintegration + +import ( + "net/http" + + "github.com/deploymenttheory/go-api-http-client/logger" +) + +// MSGraphAPIHandler implements the APIHandler interface for the Microsoft Graph API. +type Integration struct { + BaseDomain string + AuthMethodDescriptor string + Logger logger.Logger + auth authInterface +} + +// Info + +// TODO migrate strings +func (m *Integration) Domain() string { + return m.BaseDomain +} + +// TODO migrate strings +func (m *Integration) GetAuthMethodDescriptor() string { + return m.AuthMethodDescriptor +} + +// Utilities + +// TODO migrate strings +func (m *Integration) CheckRefreshToken() error { + return m.checkRefreshToken() +} + +// TODO migrate strings +func (m *Integration) PrepRequestParamsAndAuth(req *http.Request) error { + err := m.prepRequest(req) + return err +} + +// TODO migrate strings +func (m *Integration) PrepRequestBody(body interface{}, method string, endpoint string) ([]byte, error) { + return m.marshalRequest(body, method, endpoint) +} + +// TODO migrate strings +func (m *Integration) MarshalMultipartRequest(fields map[string]string, files map[string]string) ([]byte, string, error) { + return m.marshalMultipartRequest(fields, files) +} + +// TODO migrate strings +func (m *Integration) GetSessionCookies() ([]*http.Cookie, error) { + domain := m.Domain() + return m.getSessionCookies(domain) +} diff --git a/msgraph/msgraphintegration/marshall.go b/msgraph/msgraphintegration/marshall.go new file mode 100644 index 0000000..df89265 --- /dev/null +++ b/msgraph/msgraphintegration/marshall.go @@ -0,0 +1,88 @@ +// apiintegrations/msgraph/msgraph_api_request.go +package msgraphintegration + +import ( + "bytes" + "encoding/json" + "io" + "mime/multipart" + "path/filepath" + + "github.com/deploymenttheory/go-api-http-client-integrations/shared/helpers" + "go.uber.org/zap" +) + +// MarshalRequest encodes the request body as JSON for the Microsoft Graph API. +// This function takes an interface{} type body, an HTTP method, and an endpoint as input, +// and returns the marshaled JSON byte slice along with any error encountered during marshaling. +// The function ensures that the request body is always marshaled as JSON. +// It logs the JSON request body for POST, PUT, and PATCH methods using the integrated logger. +// +// Parameters: +// - body: The request body to be marshaled, of type interface{}. +// - method: The HTTP method being used for the request (e.g., "POST", "PUT", "PATCH"). +// - endpoint: The API endpoint for the request. +// +// Returns: +// - []byte: The marshaled JSON byte slice of the request body. +// - error: Any error encountered during the marshaling process. +// +// Logging: +// - Logs an error if JSON marshaling fails. +// - Logs the JSON request body for POST, PUT, and PATCH methods. +func (m *Integration) marshalRequest(body interface{}, method string, endpoint string) ([]byte, error) { + var ( + data []byte + err error + ) + + // Marshal the body as JSON + data, err = json.Marshal(body) + if err != nil { + m.Logger.Error("Failed marshaling JSON request", zap.Error(err)) + return nil, err + } + + // Log the JSON request body for POST, PUT, or PATCH methods + if method == "POST" || method == "PUT" || method == "PATCH" { + m.Logger.Debug("JSON Request Body", zap.String("Body", string(data))) + } + + return data, nil +} + +// MarshalMultipartRequest handles multipart form data encoding with secure file handling and returns the encoded body and content type. +func (m *Integration) marshalMultipartRequest(fields map[string]string, files map[string]string) ([]byte, string, error) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + for field, value := range fields { + if err := writer.WriteField(field, value); err != nil { + return nil, "", err + } + } + + for formField, filePath := range files { + file, err := helpers.SafeOpenFile(filePath) + if err != nil { + m.Logger.Error("Failed to open file securely", zap.String("file", filePath), zap.Error(err)) + return nil, "", err + } + defer file.Close() + + part, err := writer.CreateFormFile(formField, filepath.Base(filePath)) + if err != nil { + return nil, "", err + } + if _, err := io.Copy(part, file); err != nil { + return nil, "", err + } + } + + contentType := writer.FormDataContentType() + if err := writer.Close(); err != nil { + return nil, "", err + } + + return body.Bytes(), contentType, nil +} diff --git a/msgraph/msgraphintegration/request.go b/msgraph/msgraphintegration/request.go new file mode 100644 index 0000000..021b6c1 --- /dev/null +++ b/msgraph/msgraphintegration/request.go @@ -0,0 +1,23 @@ +// apiintegrations/msgraph/request.go +package msgraphintegration + +import ( + "fmt" + "net/http" +) + +// TODO func comment +func (m *Integration) prepRequest(req *http.Request) error { + req.Header.Add("Accept", m.getAcceptHeader()) + req.Header.Add("Content-Type", m.getContentTypeHeader(req.URL.String())) + req.Header.Add("User-Agent", m.getUserAgentHeader()) + + err := m.checkRefreshToken() + if err != nil { + return err + } + + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", m.auth.getTokenString())) + + return nil +} diff --git a/jamfpro/jamfprointegration/helpers.go b/shared/helpers/helpers.go similarity index 96% rename from jamfpro/jamfprointegration/helpers.go rename to shared/helpers/helpers.go index 84b7d94..d86fbe2 100644 --- a/jamfpro/jamfprointegration/helpers.go +++ b/shared/helpers/helpers.go @@ -1,4 +1,4 @@ -package jamfprointegration +package helpers import ( "fmt" From cb15fc888e42eac56a54a91d3e0d19079ec48f5b Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Wed, 19 Jun 2024 21:42:50 +0100 Subject: [PATCH 2/9] + api integratiion funcs --- jamfpro/jamfprointegration/auth.go | 14 ++ jamfpro/jamfprointegration/auth_basic.go | 46 +++++- jamfpro/jamfprointegration/headers.go | 1 + jamfpro/jamfprointegration/request.go | 17 ++- msgraph/msgraphintegration/auth.go | 62 ++++++++ msgraph/msgraphintegration/auth_basic.go | 139 ++++++++++++++++++ msgraph/msgraphintegration/constants.go | 10 ++ .../msgraphintegration/header_exceptions.go | 46 ++++++ msgraph/msgraphintegration/headers.go | 64 ++++++++ msgraph/msgraphintegration/interface.go | 3 + .../msgraph_api_exceptions_configuration.json | 3 + msgraph/msgraphintegration/request.go | 17 ++- 12 files changed, 414 insertions(+), 8 deletions(-) create mode 100644 msgraph/msgraphintegration/auth.go create mode 100644 msgraph/msgraphintegration/auth_basic.go create mode 100644 msgraph/msgraphintegration/constants.go create mode 100644 msgraph/msgraphintegration/header_exceptions.go create mode 100644 msgraph/msgraphintegration/headers.go create mode 100644 msgraph/msgraphintegration/msgraph_api_exceptions_configuration.json diff --git a/jamfpro/jamfprointegration/auth.go b/jamfpro/jamfprointegration/auth.go index 58ee3f9..758e51f 100644 --- a/jamfpro/jamfprointegration/auth.go +++ b/jamfpro/jamfprointegration/auth.go @@ -21,6 +21,20 @@ type authInterface interface { tokenEmpty() bool } +// checkRefreshToken checks and refreshes the authentication token if necessary. +// This function ensures that the authentication token is valid and not expired. If the token is empty, expired, +// or within the buffer period before expiry, it attempts to obtain a new token and validates the new token's lifetime +// against the buffer period to prevent infinite loops. +// +// Returns: +// - error: Any error encountered during the token refresh process or if the token's lifetime is shorter than the buffer period. Returns nil if no errors occur. +// +// Functionality: +// - Logs a warning if the token is empty. +// - Checks if the token is expired, within the buffer period, or empty. +// - Attempts to obtain a new token if the current token is invalid. +// - Validates the new token's lifetime against the buffer period to prevent bad token lifetime/buffer combinations. +// - Returns an error if the token refresh fails or if the new token's lifetime is shorter than the buffer period. func (j *Integration) checkRefreshToken() error { var err error diff --git a/jamfpro/jamfprointegration/auth_basic.go b/jamfpro/jamfprointegration/auth_basic.go index 50a4edf..7da3475 100644 --- a/jamfpro/jamfprointegration/auth_basic.go +++ b/jamfpro/jamfprointegration/auth_basic.go @@ -31,7 +31,21 @@ type basicAuthResponse struct { // Operations -// TODO comment +// getNewToken obtains a new bearer token from the authentication server. +// This function constructs a new HTTP request to the bearer token endpoint using the basic authentication credentials, +// sends the request, and updates the basicAuth instance with the new bearer token and its expiry time. +// +// Returns: +// - error: Any error encountered during the request, response handling, or JSON decoding. Returns nil if no errors occur. +// +// Functionality: +// - Constructs the complete bearer token endpoint URL. +// - Creates a new HTTP POST request and sets the basic authentication headers. +// - Sends the request using an HTTP client and checks the response status. +// - Decodes the JSON response to obtain the bearer token and its expiry time. +// - Updates the basicAuth instance with the new bearer token and its expiry time. +// - Logs the successful token retrieval with the expiry time and duration. +// // TODO migrate strings func (a *basicAuth) getNewToken() error { client := http.Client{} @@ -70,24 +84,40 @@ func (a *basicAuth) getNewToken() error { return nil } -// TODO comment +// getTokenString returns the current bearer token as a string. +// This function provides access to the current bearer token stored in the basicAuth instance. +// +// Returns: +// - string: The current bearer token. func (a *basicAuth) getTokenString() string { return a.bearerToken } -// TODO comment +// getExpiryTime returns the expiry time of the current bearer token. +// This function provides access to the expiry time of the current bearer token stored in the basicAuth instance. +// +// Returns: +// - time.Time: The expiry time of the current bearer token. func (a *basicAuth) getExpiryTime() time.Time { return a.bearerTokenExpiryTime } // Utils -// TODO comment +// tokenExpired checks if the current bearer token has expired. +// This function compares the current time with the bearer token's expiry time to determine if the token has expired. +// +// Returns: +// - bool: True if the bearer token has expired, false otherwise. func (a *basicAuth) tokenExpired() bool { return a.bearerTokenExpiryTime.Before(time.Now()) } -// TODO comment +// tokenInBuffer checks if the current bearer token is within the buffer period before expiry. +// This function calculates the remaining time until the token expires and compares it with the buffer period. +// +// Returns: +// - bool: True if the bearer token is within the buffer period, false otherwise. func (a *basicAuth) tokenInBuffer() bool { if time.Until(a.bearerTokenExpiryTime) <= a.bufferPeriod { return true @@ -96,7 +126,11 @@ func (a *basicAuth) tokenInBuffer() bool { return false } -// TODO comment +// tokenEmpty checks if the current bearer token is empty. +// This function determines if the bearer token string stored in the basicAuth instance is empty. +// +// Returns: +// - bool: True if the bearer token is empty, false otherwise. func (a *basicAuth) tokenEmpty() bool { if a.bearerToken == "" { return true diff --git a/jamfpro/jamfprointegration/headers.go b/jamfpro/jamfprointegration/headers.go index ed6dc86..609655d 100644 --- a/jamfpro/jamfprointegration/headers.go +++ b/jamfpro/jamfprointegration/headers.go @@ -53,6 +53,7 @@ func (j *Integration) getAcceptHeader() string { return weightedAcceptHeader } +// getUserAgentHeader returns the User-Agent header string for the Jamf Pro API. func (j *Integration) getUserAgentHeader() string { return "go-api-http-client-jamfpro-integration" } diff --git a/jamfpro/jamfprointegration/request.go b/jamfpro/jamfprointegration/request.go index 3d3b6bb..6f8dc44 100644 --- a/jamfpro/jamfprointegration/request.go +++ b/jamfpro/jamfprointegration/request.go @@ -5,7 +5,22 @@ import ( "net/http" ) -// TODO func comment +// prepRequest prepares an HTTP request by setting the necessary headers and handling authorization. +// This function adds headers for Accept, Content-Type, User-Agent, and Authorization based on the Integration's methods +// and checks for token refresh if needed. +// +// Parameters: +// - req: A pointer to the http.Request that needs to be prepared. +// +// Returns: +// - error: Any error encountered while checking the refresh token or setting headers. Returns nil if no errors occur. +// +// Functionality: +// - Adds an "Accept" header based on the Integration's getAcceptHeader method. +// - Adds a "Content-Type" header based on the Integration's getContentTypeHeader method, which depends on the request URL. +// - Adds a "User-Agent" header based on the Integration's getUserAgentHeader method. +// - Checks and refreshes the token if necessary using the Integration's checkRefreshToken method. +// - Adds an "Authorization" header with a Bearer token obtained from the Integration's auth.getTokenString method. func (j *Integration) prepRequest(req *http.Request) error { req.Header.Add("Accept", j.getAcceptHeader()) req.Header.Add("Content-Type", j.getContentTypeHeader(req.URL.String())) diff --git a/msgraph/msgraphintegration/auth.go b/msgraph/msgraphintegration/auth.go new file mode 100644 index 0000000..0d6bb62 --- /dev/null +++ b/msgraph/msgraphintegration/auth.go @@ -0,0 +1,62 @@ +package msgraphintegration + +import ( + "errors" + "time" +) + +const ( + tokenEmptyWarnString = "token empty before processing - disregard if first run" +) + +// authInterface defines the methods required to satify the authentication interface. +type authInterface interface { + // Token Operations + getNewToken() error + getTokenString() string + getExpiryTime() time.Time + + // Token Utils + tokenExpired() bool + tokenInBuffer() bool + tokenEmpty() bool +} + +// checkRefreshToken checks and refreshes the authentication token if necessary. +// This function ensures that the authentication token is valid and not expired. If the token is empty, expired, +// or within the buffer period before expiry, it attempts to obtain a new token and validates the new token's lifetime +// against the buffer period to prevent infinite loops. +// +// Returns: +// - error: Any error encountered during the token refresh process or if the token's lifetime is shorter than the buffer period. Returns nil if no errors occur. +// +// Functionality: +// - Logs a warning if the token is empty. +// - Checks if the token is expired, within the buffer period, or empty. +// - Attempts to obtain a new token if the current token is invalid. +// - Validates the new token's lifetime against the buffer period to prevent bad token lifetime/buffer combinations. +// - Returns an error if the token refresh fails or if the new token's lifetime is shorter than the buffer period. +func (m *Integration) checkRefreshToken() error { + var err error + + if m.auth.tokenEmpty() { + m.Logger.Warn(tokenEmptyWarnString) + } + + if m.auth.tokenExpired() || m.auth.tokenInBuffer() || m.auth.tokenEmpty() { + err = m.auth.getNewToken() + + if err != nil { + return err + } + + // Protects against bad token lifetime/buffer combinations (infinite loops) + if m.auth.tokenExpired() || m.auth.tokenInBuffer() { + return errors.New("token lifetime is shorter than buffer period. please adjust parameters") + } + + return nil + } + + return nil +} diff --git a/msgraph/msgraphintegration/auth_basic.go b/msgraph/msgraphintegration/auth_basic.go new file mode 100644 index 0000000..f418cb3 --- /dev/null +++ b/msgraph/msgraphintegration/auth_basic.go @@ -0,0 +1,139 @@ +package msgraphintegration + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/deploymenttheory/go-api-http-client/logger" + "go.uber.org/zap" +) + +type basicAuth struct { + // Set + baseDomain string + username string + password string + bufferPeriod time.Duration + logger logger.Logger + + // Computed + basicToken string + bearerToken string + bearerTokenExpiryTime time.Time +} + +type basicAuthResponse struct { + Token string `json:"token"` + Expires time.Time `json:"expires"` +} + +// Operations + +// getNewToken obtains a new bearer token from the authentication server. +// This function constructs a new HTTP request to the bearer token endpoint using the basic authentication credentials, +// sends the request, and updates the basicAuth instance with the new bearer token and its expiry time. +// +// Returns: +// - error: Any error encountered during the request, response handling, or JSON decoding. Returns nil if no errors occur. +// +// Functionality: +// - Constructs the complete bearer token endpoint URL. +// - Creates a new HTTP POST request and sets the basic authentication headers. +// - Sends the request using an HTTP client and checks the response status. +// - Decodes the JSON response to obtain the bearer token and its expiry time. +// - Updates the basicAuth instance with the new bearer token and its expiry time. +// - Logs the successful token retrieval with the expiry time and duration. +// +// TODO migrate strings +func (a *basicAuth) getNewToken() error { + client := http.Client{} + + completeBearerEndpoint := a.baseDomain + bearerTokenEndpoint + req, err := http.NewRequest("POST", completeBearerEndpoint, nil) + if err != nil { + return err + } + + req.SetBasicAuth(a.username, a.password) + + resp, err := client.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received non-OK response status: %d", resp.StatusCode) + } + + tokenResp := &basicAuthResponse{} + err = json.NewDecoder(resp.Body).Decode(tokenResp) + if err != nil { + return err + } + + a.bearerToken = tokenResp.Token + a.bearerTokenExpiryTime = tokenResp.Expires + tokenDuration := time.Until(a.bearerTokenExpiryTime) + + a.logger.Info("Token obtained successfully", zap.Time("Expiry", a.bearerTokenExpiryTime), zap.Duration("Duration", tokenDuration)) + + return nil +} + +// getTokenString returns the current bearer token as a string. +// This function provides access to the current bearer token stored in the basicAuth instance. +// +// Returns: +// - string: The current bearer token. +func (a *basicAuth) getTokenString() string { + return a.bearerToken +} + +// getExpiryTime returns the expiry time of the current bearer token. +// This function provides access to the expiry time of the current bearer token stored in the basicAuth instance. +// +// Returns: +// - time.Time: The expiry time of the current bearer token. +func (a *basicAuth) getExpiryTime() time.Time { + return a.bearerTokenExpiryTime +} + +// Utils + +// tokenExpired checks if the current bearer token has expired. +// This function compares the current time with the bearer token's expiry time to determine if the token has expired. +// +// Returns: +// - bool: True if the bearer token has expired, false otherwise. +func (a *basicAuth) tokenExpired() bool { + return a.bearerTokenExpiryTime.Before(time.Now()) +} + +// tokenInBuffer checks if the current bearer token is within the buffer period before expiry. +// This function calculates the remaining time until the token expires and compares it with the buffer period. +// +// Returns: +// - bool: True if the bearer token is within the buffer period, false otherwise. +func (a *basicAuth) tokenInBuffer() bool { + if time.Until(a.bearerTokenExpiryTime) <= a.bufferPeriod { + return true + } + + return false +} + +// tokenEmpty checks if the current bearer token is empty. +// This function determines if the bearer token string stored in the basicAuth instance is empty. +// +// Returns: +// - bool: True if the bearer token is empty, false otherwise. +func (a *basicAuth) tokenEmpty() bool { + if a.bearerToken == "" { + return true + } + return false +} diff --git a/msgraph/msgraphintegration/constants.go b/msgraph/msgraphintegration/constants.go new file mode 100644 index 0000000..4d8141a --- /dev/null +++ b/msgraph/msgraphintegration/constants.go @@ -0,0 +1,10 @@ +package msgraphintegration + +// Endpoint constants represent the URL suffixes used for Jamf API token interactions. +const ( + // Auth + oAuthTokenEndpoint string = "/oauth2/v2.0/token" + bearerTokenEndpoint string = "graph.microsoft.com" + invalidateTokenEndpoint string = "graph.microsoft.com" + oAuthTokenScope string = "https://graph.microsoft.com/.default" +) diff --git a/msgraph/msgraphintegration/header_exceptions.go b/msgraph/msgraphintegration/header_exceptions.go new file mode 100644 index 0000000..c7106c6 --- /dev/null +++ b/msgraph/msgraphintegration/header_exceptions.go @@ -0,0 +1,46 @@ +// msgraph/msgraphintegration/header_exceptions.go +package msgraphintegration + +import ( + _ "embed" + + "encoding/json" + "log" +) + +// EndpointConfig is a struct that holds configuration details for a specific API endpoint. +// It includes what type of content it can accept and what content type it should send. +type EndpointConfig struct { + Accept string `json:"accept"` // Accept specifies the MIME type the endpoint can handle in responses. + ContentType *string `json:"content_type"` // ContentType, if not nil, specifies the MIME type to set for requests sent to the endpoint. A pointer is used to distinguish between a missing field and an empty string. +} + +// ConfigMap is a map that associates endpoint URL patterns with their corresponding configurations. +// The map's keys are strings that identify the endpoint, and the values are EndpointConfig structs +// that hold the configuration for that endpoint. +type ConfigMap map[string]EndpointConfig + +// Variables +var configMap ConfigMap + +// Embedded Resources +// +//go:embed msgraph_api_exceptions_configuration.json +var graph_api_exceptions_configuration []byte + +// init is invoked automatically on package initialization and is responsible for +// setting up the default state of the package by loading the api exceptions configuration. +func init() { + // Load the default configuration from an embedded resource. + err := loadAPIExceptionsConfiguration() + if err != nil { + log.Fatalf("Error loading Microsoft Graph API exceptions configuration: %s", err) + } +} + +// loadAPIExceptionsConfiguration reads and unmarshals the graph_api_exceptions_configuration JSON data from an embedded file +// into the configMap variable, which holds the exceptions configuration for endpoint-specific headers. +func loadAPIExceptionsConfiguration() error { + // Unmarshal the embedded default configuration into the global configMap. + return json.Unmarshal(graph_api_exceptions_configuration, &configMap) +} diff --git a/msgraph/msgraphintegration/headers.go b/msgraph/msgraphintegration/headers.go new file mode 100644 index 0000000..c390ec1 --- /dev/null +++ b/msgraph/msgraphintegration/headers.go @@ -0,0 +1,64 @@ +// msgraph/msgraphintegration/headers.go +package msgraphintegration + +import ( + "strings" + + "github.com/deploymenttheory/go-api-http-client/logger" + "go.uber.org/zap" +) + +// getContentTypeHeader determines the appropriate Content-Type header for a given API endpoint. +// It attempts to find a content type that matches the endpoint prefix in the global configMap. +// If a match is found and the content type is defined (not nil), it returns the specified content type. +// If the endpoint does not match any of the predefined patterns, "application/json" is used as a fallback. +// This method logs the decision process at various stages for debugging purposes. +func (m *Integration) getContentTypeHeader(endpoint string, log logger.Logger) string { + // Dynamic lookup from configuration should be the first priority + for key, config := range configMap { + if strings.HasPrefix(endpoint, key) { + if config.ContentType != nil { + m.Logger.Debug("Content-Type for endpoint found in configMap", zap.String("endpoint", endpoint), zap.String("content_type", *config.ContentType)) + return *config.ContentType + } + m.Logger.Debug("Content-Type for endpoint is nil in configMap, handling as special case", zap.String("endpoint", endpoint)) + // If a nil ContentType is an expected case, do not set Content-Type header. + return "" // Return empty to indicate no Content-Type should be set. + } + } + + // Fallback to JSON if no other match is found. + m.Logger.Debug("Content-Type for endpoint not found in configMap or standard patterns, using default JSON", zap.String("endpoint", endpoint)) + return "application/json" +} + +// GetAcceptHeader constructs and returns a weighted Accept header string for HTTP requests. +// The Accept header indicates the MIME types that the client can process and prioritizes them +// based on the quality factor (q) parameter. Higher q-values signal greater preference. +// This function specifies a range of MIME types with their respective weights, ensuring that +// the server is informed of the client's versatile content handling capabilities while +// indicating a preference for XML. The specified MIME types cover common content formats like +// images, JSON, XML, HTML, plain text, and certificates, with a fallback option for all other types. +func (m *Integration) getAcceptHeader() string { + weightedAcceptHeader := "application/x-x509-ca-cert;q=0.95," + + "application/pkix-cert;q=0.94," + + "application/pem-certificate-chain;q=0.93," + + "application/octet-stream;q=0.8," + // For general binary files + "image/png;q=0.75," + + "image/jpeg;q=0.74," + + "image/*;q=0.7," + + "application/xml;q=0.65," + + "text/xml;q=0.64," + + "text/xml;charset=UTF-8;q=0.63," + + "application/json;q=0.5," + + "text/html;q=0.5," + + "text/plain;q=0.4," + + "*/*;q=0.05" // Fallback for any other types + + return weightedAcceptHeader +} + +// getUserAgentHeader returns the User-Agent header string for the Microsoft Graph API. +func (m *Integration) getUserAgentHeader() string { + return "go-api-http-client-msgraph-integration" +} diff --git a/msgraph/msgraphintegration/interface.go b/msgraph/msgraphintegration/interface.go index badd89a..5054353 100644 --- a/msgraph/msgraphintegration/interface.go +++ b/msgraph/msgraphintegration/interface.go @@ -13,6 +13,9 @@ type Integration struct { AuthMethodDescriptor string Logger logger.Logger auth authInterface + + TenantID string // TenantID used for constructing the authentication endpoint. + TenantName string // TenantName used for constructing the authentication endpoint. } // Info diff --git a/msgraph/msgraphintegration/msgraph_api_exceptions_configuration.json b/msgraph/msgraphintegration/msgraph_api_exceptions_configuration.json new file mode 100644 index 0000000..0db3279 --- /dev/null +++ b/msgraph/msgraphintegration/msgraph_api_exceptions_configuration.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/msgraph/msgraphintegration/request.go b/msgraph/msgraphintegration/request.go index 021b6c1..e4697cf 100644 --- a/msgraph/msgraphintegration/request.go +++ b/msgraph/msgraphintegration/request.go @@ -6,7 +6,22 @@ import ( "net/http" ) -// TODO func comment +// prepRequest prepares an HTTP request by setting the necessary headers and handling authorization. +// This function adds headers for Accept, Content-Type, User-Agent, and Authorization based on the Integration's methods +// and checks for token refresh if needed. +// +// Parameters: +// - req: A pointer to the http.Request that needs to be prepared. +// +// Returns: +// - error: Any error encountered while checking the refresh token or setting headers. Returns nil if no errors occur. +// +// Functionality: +// - Adds an "Accept" header based on the Integration's getAcceptHeader method. +// - Adds a "Content-Type" header based on the Integration's getContentTypeHeader method, which depends on the request URL. +// - Adds a "User-Agent" header based on the Integration's getUserAgentHeader method. +// - Checks and refreshes the token if necessary using the Integration's checkRefreshToken method. +// - Adds an "Authorization" header with a Bearer token obtained from the Integration's auth.getTokenString method. func (m *Integration) prepRequest(req *http.Request) error { req.Header.Add("Accept", m.getAcceptHeader()) req.Header.Add("Content-Type", m.getContentTypeHeader(req.URL.String())) From 6f4fe676f9fc59c76214dfd9b5db05ca65d2169b Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Wed, 19 Jun 2024 22:06:12 +0100 Subject: [PATCH 3/9] chore: Update Microsoft Graph API authentication URL construction and request body --- jamfpro/jamfprointegration/urls.go | 3 +- msgraph/msgraphintegration/auth_basic.go | 31 +++-- msgraph/msgraphintegration/auth_oauth.go | 149 +++++++++++++++++++++++ msgraph/msgraphintegration/constants.go | 1 + msgraph/msgraphintegration/interface.go | 8 +- msgraph/msgraphintegration/urls.go | 7 ++ 6 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 msgraph/msgraphintegration/auth_oauth.go create mode 100644 msgraph/msgraphintegration/urls.go diff --git a/jamfpro/jamfprointegration/urls.go b/jamfpro/jamfprointegration/urls.go index 7c2f102..84af565 100644 --- a/jamfpro/jamfprointegration/urls.go +++ b/jamfpro/jamfprointegration/urls.go @@ -1,8 +1,7 @@ // jamfpro/jamfprointegration/urls.go package jamfprointegration -// SetBaseDomain returns the appropriate base domain for URL construction. -// It uses j.OverrideBaseDomain if set, otherwise falls back to DefaultBaseDomain. +// GetBaseDomain returns the base domain for the Jamf Pro integration. func (j *Integration) GetBaseDomain() string { return j.BaseDomain } diff --git a/msgraph/msgraphintegration/auth_basic.go b/msgraph/msgraphintegration/auth_basic.go index f418cb3..47c36e3 100644 --- a/msgraph/msgraphintegration/auth_basic.go +++ b/msgraph/msgraphintegration/auth_basic.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "strings" "time" "github.com/deploymenttheory/go-api-http-client/logger" @@ -15,6 +17,7 @@ type basicAuth struct { baseDomain string username string password string + tenantID string bufferPeriod time.Duration logger logger.Logger @@ -31,33 +34,41 @@ type basicAuthResponse struct { // Operations -// getNewToken obtains a new bearer token from the authentication server. -// This function constructs a new HTTP request to the bearer token endpoint using the basic authentication credentials, +// getNewToken obtains a new bearer token from the Microsoft Graph API authentication server. +// This function constructs a new HTTP request to the OAuth2.0 token endpoint using the basic authentication credentials, // sends the request, and updates the basicAuth instance with the new bearer token and its expiry time. // // Returns: // - error: Any error encountered during the request, response handling, or JSON decoding. Returns nil if no errors occur. // // Functionality: -// - Constructs the complete bearer token endpoint URL. -// - Creates a new HTTP POST request and sets the basic authentication headers. +// - Constructs the complete OAuth2.0 token endpoint URL using the tenantID. +// - Logs the constructed authentication URL. +// - Creates a new HTTP POST request and sets the form data with grant type, scope, username, and password for the request body. // - Sends the request using an HTTP client and checks the response status. // - Decodes the JSON response to obtain the bearer token and its expiry time. // - Updates the basicAuth instance with the new bearer token and its expiry time. // - Logs the successful token retrieval with the expiry time and duration. -// -// TODO migrate strings func (a *basicAuth) getNewToken() error { client := http.Client{} - completeBearerEndpoint := a.baseDomain + bearerTokenEndpoint - req, err := http.NewRequest("POST", completeBearerEndpoint, nil) + constructedBearerAuthEndpoint := fmt.Sprintf("%s/%s%s", baseAuthURL, a.tenantID, oAuthTokenEndpoint) + + a.logger.Info("constructed Microsoft Graph API authentication URL", zap.String("URL", constructedBearerAuthEndpoint)) + + formData := url.Values{ + "grant_type": {"password"}, + "scope": {oAuthTokenScope}, + "username": {a.username}, + "password": {a.password}, + } + + req, err := http.NewRequest("POST", constructedBearerAuthEndpoint, strings.NewReader(formData.Encode())) if err != nil { return err } - req.SetBasicAuth(a.username, a.password) - + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") resp, err := client.Do(req) if err != nil { return err diff --git a/msgraph/msgraphintegration/auth_oauth.go b/msgraph/msgraphintegration/auth_oauth.go new file mode 100644 index 0000000..56d034c --- /dev/null +++ b/msgraph/msgraphintegration/auth_oauth.go @@ -0,0 +1,149 @@ +package msgraphintegration + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/deploymenttheory/go-api-http-client/logger" +) + +type oauth struct { + // Set + baseDomain string + clientId string + clientSecret string + tenantID string + bufferPeriod time.Duration + Logger logger.Logger + + // Computed + expiryTime time.Time + token string +} + +// OAuthResponse represents the response structure when obtaining an OAuth access token from Microsoft Graph. +type OAuthResponse struct { + AccessToken string `json:"access_token"` + ExpiresIn int64 `json:"expires_in"` + TokenType string `json:"token_type"` +} + +// Operations + +// getNewToken obtains a new bearer token from the Microsoft Graph authentication server. +// This function constructs a new HTTP request to the OAuth2.0 token endpoint using the client credentials, +// sends the request, and updates the oauth instance with the new bearer token and its expiry time. +// +// Returns: +// - error: Any error encountered during the request, response handling, or JSON decoding. Returns nil if no errors occur. +// +// Functionality: +// - Constructs the complete OAuth2.0 token endpoint URL using the tenantID. +// - Creates a new HTTP POST request and sets the form data with client ID, client secret, and grant type. +// - Sends the request using an HTTP client and checks the response status. +// - Decodes the JSON response to obtain the bearer token and its expiry time. +// - Updates the oauth instance with the new bearer token and its expiry time. +func (a *oauth) getNewToken() error { + client := http.Client{} + data := url.Values{} + + data.Set("client_id", a.clientId) + data.Set("client_secret", a.clientSecret) + data.Set("grant_type", "client_credentials") + data.Set("scope", oAuthTokenScope) + + oauthCompleteEndpoint := fmt.Sprintf("%s/%s%s", baseAuthURL, a.tenantID, oAuthTokenEndpoint) + req, err := http.NewRequest("POST", oauthCompleteEndpoint, strings.NewReader(data.Encode())) + if err != nil { + return err + } + + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := client.Do(req) + if err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return fmt.Errorf("bad request: %v", resp) + } + + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + + oauthResp := &OAuthResponse{} + err = json.Unmarshal(bodyBytes, oauthResp) + if err != nil { + return fmt.Errorf("failed to decode OAuth response: %w", err) + } + + if oauthResp.AccessToken == "" { + return fmt.Errorf("empty access token received") + } + + expiresIn := time.Duration(oauthResp.ExpiresIn) * time.Second + a.expiryTime = time.Now().Add(expiresIn) + a.token = oauthResp.AccessToken + + return nil +} + +// getTokenString returns the current bearer token as a string. +// This function provides access to the current bearer token stored in the oauth instance. +// +// Returns: +// - string: The current bearer token. +func (a *oauth) getTokenString() string { + return a.token +} + +// getExpiryTime returns the expiry time of the current bearer token. +// This function provides access to the expiry time of the current bearer token stored in the oauth instance. +// +// Returns: +// - time.Time: The expiry time of the current bearer token. +func (a *oauth) getExpiryTime() time.Time { + return a.expiryTime +} + +// Utils + +// tokenExpired checks if the current bearer token has expired. +// This function compares the current time with the bearer token's expiry time to determine if the token has expired. +// +// Returns: +// - bool: True if the bearer token has expired, false otherwise. +func (a *oauth) tokenExpired() bool { + return a.expiryTime.Before(time.Now()) +} + +// tokenInBuffer checks if the current bearer token is within the buffer period before expiry. +// This function calculates the remaining time until the token expires and compares it with the buffer period. +// +// Returns: +// - bool: True if the bearer token is within the buffer period, false otherwise. +func (a *oauth) tokenInBuffer() bool { + return time.Until(a.expiryTime) <= a.bufferPeriod +} + +// tokenEmpty checks if the current bearer token is empty. +// This function determines if the bearer token string stored in the oauth instance is empty. +// +// Returns: +// - bool: True if the bearer token is empty, false otherwise. +func (a *oauth) tokenEmpty() bool { + return a.token == "" +} diff --git a/msgraph/msgraphintegration/constants.go b/msgraph/msgraphintegration/constants.go index 4d8141a..9b5a8e6 100644 --- a/msgraph/msgraphintegration/constants.go +++ b/msgraph/msgraphintegration/constants.go @@ -7,4 +7,5 @@ const ( bearerTokenEndpoint string = "graph.microsoft.com" invalidateTokenEndpoint string = "graph.microsoft.com" oAuthTokenScope string = "https://graph.microsoft.com/.default" + baseAuthURL string = "https://login.microsoftonline.com" ) diff --git a/msgraph/msgraphintegration/interface.go b/msgraph/msgraphintegration/interface.go index 5054353..70456f6 100644 --- a/msgraph/msgraphintegration/interface.go +++ b/msgraph/msgraphintegration/interface.go @@ -7,15 +7,13 @@ import ( "github.com/deploymenttheory/go-api-http-client/logger" ) -// MSGraphAPIHandler implements the APIHandler interface for the Microsoft Graph API. +// Integration implements the APIHandler interface for the Microsoft Graph API. type Integration struct { - BaseDomain string + TenantID string // TenantID used for constructing the authentication endpoint. + TenantName string // TenantName used for constructing the authentication endpoint. AuthMethodDescriptor string Logger logger.Logger auth authInterface - - TenantID string // TenantID used for constructing the authentication endpoint. - TenantName string // TenantName used for constructing the authentication endpoint. } // Info diff --git a/msgraph/msgraphintegration/urls.go b/msgraph/msgraphintegration/urls.go new file mode 100644 index 0000000..9c47420 --- /dev/null +++ b/msgraph/msgraphintegration/urls.go @@ -0,0 +1,7 @@ +// apiintegrations/msgraph/urls.go +package msgraphintegration + +// GetBaseDomain returns the base domain for the Jamf Pro integration. +func (m *Integration) GetBaseDomain() string { + return m.BaseDomain +} From b5b15395a6dcd1918524d98bdff733a932c77fb1 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Wed, 19 Jun 2024 22:09:31 +0100 Subject: [PATCH 4/9] chore: Refactor Microsoft Graph integration code for improved authentication and URL handling --- msgraph/msgraphintegration/auth_oauth.go | 1 - msgraph/msgraphintegration/urls.go | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/msgraph/msgraphintegration/auth_oauth.go b/msgraph/msgraphintegration/auth_oauth.go index 56d034c..08b44af 100644 --- a/msgraph/msgraphintegration/auth_oauth.go +++ b/msgraph/msgraphintegration/auth_oauth.go @@ -15,7 +15,6 @@ import ( type oauth struct { // Set - baseDomain string clientId string clientSecret string tenantID string diff --git a/msgraph/msgraphintegration/urls.go b/msgraph/msgraphintegration/urls.go index 9c47420..e7488c9 100644 --- a/msgraph/msgraphintegration/urls.go +++ b/msgraph/msgraphintegration/urls.go @@ -1,7 +1,7 @@ // apiintegrations/msgraph/urls.go package msgraphintegration -// GetBaseDomain returns the base domain for the Jamf Pro integration. -func (m *Integration) GetBaseDomain() string { - return m.BaseDomain +// GetTenantName returns the tenant name for the Microsoft Graph integration. +func (m *Integration) GetTenantName() string { + return m.TenantName } From 02f9eae8e96491362e9d5e7a72d64b73623725b8 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Wed, 19 Jun 2024 22:10:31 +0100 Subject: [PATCH 5/9] Refactor Integration.Domain() method to use TenantName instead of BaseDomain --- msgraph/msgraphintegration/interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgraph/msgraphintegration/interface.go b/msgraph/msgraphintegration/interface.go index 70456f6..ad82873 100644 --- a/msgraph/msgraphintegration/interface.go +++ b/msgraph/msgraphintegration/interface.go @@ -20,7 +20,7 @@ type Integration struct { // TODO migrate strings func (m *Integration) Domain() string { - return m.BaseDomain + return m.TenantName } // TODO migrate strings From da9834ccc88ee103a2adbe20993ef14a63848d19 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Wed, 19 Jun 2024 22:15:52 +0100 Subject: [PATCH 6/9] Refactor Microsoft Graph integration code for improved authentication and URL handling --- msgraph/msgraphintegration/auth.go | 1 + msgraph/msgraphintegration/auth_basic.go | 1 + msgraph/msgraphintegration/auth_oauth.go | 1 + msgraph/msgraphintegration/builders.go | 104 +++++++++++++++++++++++ msgraph/msgraphintegration/constants.go | 1 + msgraph/msgraphintegration/interface.go | 6 -- 6 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 msgraph/msgraphintegration/builders.go diff --git a/msgraph/msgraphintegration/auth.go b/msgraph/msgraphintegration/auth.go index 0d6bb62..691ec5d 100644 --- a/msgraph/msgraphintegration/auth.go +++ b/msgraph/msgraphintegration/auth.go @@ -1,3 +1,4 @@ +// msgraph/msgraphintegration/auth.go package msgraphintegration import ( diff --git a/msgraph/msgraphintegration/auth_basic.go b/msgraph/msgraphintegration/auth_basic.go index 47c36e3..6458a6d 100644 --- a/msgraph/msgraphintegration/auth_basic.go +++ b/msgraph/msgraphintegration/auth_basic.go @@ -1,3 +1,4 @@ +// msgraph/msgraphintegration/auth_basic.go package msgraphintegration import ( diff --git a/msgraph/msgraphintegration/auth_oauth.go b/msgraph/msgraphintegration/auth_oauth.go index 08b44af..3d7a071 100644 --- a/msgraph/msgraphintegration/auth_oauth.go +++ b/msgraph/msgraphintegration/auth_oauth.go @@ -1,3 +1,4 @@ +// msgraph/msgraphintegration/auth_oauth.go package msgraphintegration import ( diff --git a/msgraph/msgraphintegration/builders.go b/msgraph/msgraphintegration/builders.go new file mode 100644 index 0000000..334b740 --- /dev/null +++ b/msgraph/msgraphintegration/builders.go @@ -0,0 +1,104 @@ +// msgraph/msgraphintegration/builders.go +package msgraphintegration + +import ( + "time" + + "github.com/deploymenttheory/go-api-http-client/logger" +) + +// BuildIntegrationWithOAuth constructs an Integration instance using OAuth2.0 authentication. +// It sets up the OAuth2.0 authentication method with the provided client ID, client secret, and tenant ID. +// Checks the token refresh status upon creation. +// +// Parameters: +// - msGraphBaseDomain: The base domain for the Microsoft Graph API. +// - logger: A logger instance for logging purposes. +// - bufferPeriod: The buffer period before token expiry to refresh the token. +// - clientId: The client ID for OAuth2.0 authentication. +// - clientSecret: The client secret for OAuth2.0 authentication. +// - tenantID: The tenant ID for the Microsoft Graph API. +// +// Returns: +// - *Integration: A pointer to the constructed Integration instance. +// - error: Any error encountered during the token refresh check. +func BuildIntegrationWithOAuth(msGraphBaseDomain string, logger logger.Logger, bufferPeriod time.Duration, clientId string, clientSecret string, tenantID string) (*Integration, error) { + integration := Integration{ + BaseDomain: msGraphBaseDomain, + Logger: logger, + AuthMethodDescriptor: "oauth2", + } + + integration.BuildOAuth(clientId, clientSecret, bufferPeriod, tenantID) + err := integration.CheckRefreshToken() + + return &integration, err +} + +// BuildIntegrationWithBasicAuth constructs an Integration instance using Basic Authentication. +// It sets up the basic authentication method with the provided username, password, and tenant ID. +// Checks the token refresh status upon creation. +// +// Parameters: +// - msGraphBaseDomain: The base domain for the Microsoft Graph API. +// - logger: A logger instance for logging purposes. +// - bufferPeriod: The buffer period before token expiry to refresh the token. +// - username: The username for basic authentication. +// - password: The password for basic authentication. +// - tenantID: The tenant ID for the Microsoft Graph API. +// +// Returns: +// - *Integration: A pointer to the constructed Integration instance. +// - error: Any error encountered during the token refresh check. +func BuildIntegrationWithBasicAuth(msGraphBaseDomain string, logger logger.Logger, bufferPeriod time.Duration, username string, password string, tenantID string) (*Integration, error) { + integration := Integration{ + BaseDomain: msGraphBaseDomain, + Logger: logger, + AuthMethodDescriptor: "basic", + } + + integration.BuildBasicAuth(username, password, bufferPeriod, tenantID) + err := integration.CheckRefreshToken() + + return &integration, err +} + +// BuildOAuth sets up the OAuth2.0 authentication method for the Integration instance. +// +// Parameters: +// - clientId: The client ID for OAuth2.0 authentication. +// - clientSecret: The client secret for OAuth2.0 authentication. +// - bufferPeriod: The buffer period before token expiry to refresh the token. +// - tenantID: The tenant ID for the Microsoft Graph API. +func (j *Integration) BuildOAuth(clientId string, clientSecret string, bufferPeriod time.Duration, tenantID string) { + authInterface := oauth{ + clientId: clientId, + clientSecret: clientSecret, + bufferPeriod: bufferPeriod, + baseDomain: j.BaseDomain, + Logger: j.Logger, + tenantID: tenantID, + } + + j.auth = &authInterface +} + +// BuildBasicAuth sets up the basic authentication method for the Integration instance. +// +// Parameters: +// - username: The username for basic authentication. +// - password: The password for basic authentication. +// - bufferPeriod: The buffer period before token expiry to refresh the token. +// - tenantID: The tenant ID for the Microsoft Graph API. +func (j *Integration) BuildBasicAuth(username string, password string, bufferPeriod time.Duration, tenantID string) { + authInterface := basicAuth{ + username: username, + password: password, + bufferPeriod: bufferPeriod, + logger: j.Logger, + baseDomain: j.BaseDomain, + tenantID: tenantID, + } + + j.auth = &authInterface +} diff --git a/msgraph/msgraphintegration/constants.go b/msgraph/msgraphintegration/constants.go index 9b5a8e6..52320f2 100644 --- a/msgraph/msgraphintegration/constants.go +++ b/msgraph/msgraphintegration/constants.go @@ -1,3 +1,4 @@ +// msgraph/msgraphintegration/constants.go package msgraphintegration // Endpoint constants represent the URL suffixes used for Jamf API token interactions. diff --git a/msgraph/msgraphintegration/interface.go b/msgraph/msgraphintegration/interface.go index ad82873..dc5a5b6 100644 --- a/msgraph/msgraphintegration/interface.go +++ b/msgraph/msgraphintegration/interface.go @@ -50,9 +50,3 @@ func (m *Integration) PrepRequestBody(body interface{}, method string, endpoint func (m *Integration) MarshalMultipartRequest(fields map[string]string, files map[string]string) ([]byte, string, error) { return m.marshalMultipartRequest(fields, files) } - -// TODO migrate strings -func (m *Integration) GetSessionCookies() ([]*http.Cookie, error) { - domain := m.Domain() - return m.getSessionCookies(domain) -} From 6f16955e8770b40ba904a61cfb0d28a7ad170888 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Wed, 19 Jun 2024 22:17:01 +0100 Subject: [PATCH 7/9] Refactor getContentTypeHeader method in msgraphintegration package --- msgraph/msgraphintegration/headers.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/msgraph/msgraphintegration/headers.go b/msgraph/msgraphintegration/headers.go index c390ec1..3199470 100644 --- a/msgraph/msgraphintegration/headers.go +++ b/msgraph/msgraphintegration/headers.go @@ -4,7 +4,6 @@ package msgraphintegration import ( "strings" - "github.com/deploymenttheory/go-api-http-client/logger" "go.uber.org/zap" ) @@ -13,7 +12,7 @@ import ( // If a match is found and the content type is defined (not nil), it returns the specified content type. // If the endpoint does not match any of the predefined patterns, "application/json" is used as a fallback. // This method logs the decision process at various stages for debugging purposes. -func (m *Integration) getContentTypeHeader(endpoint string, log logger.Logger) string { +func (m *Integration) getContentTypeHeader(endpoint string) string { // Dynamic lookup from configuration should be the first priority for key, config := range configMap { if strings.HasPrefix(endpoint, key) { From fef0ad7b21a46e0609ad66845bb676cf5baf3d40 Mon Sep 17 00:00:00 2001 From: Joseph Little Date: Thu, 20 Jun 2024 09:08:09 +0100 Subject: [PATCH 8/9] renamed .Domain() to .GetFQDN() --- msgraph/msgraphintegration/interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgraph/msgraphintegration/interface.go b/msgraph/msgraphintegration/interface.go index dc5a5b6..ab2ddda 100644 --- a/msgraph/msgraphintegration/interface.go +++ b/msgraph/msgraphintegration/interface.go @@ -19,7 +19,7 @@ type Integration struct { // Info // TODO migrate strings -func (m *Integration) Domain() string { +func (m *Integration) GetFQDN() string { return m.TenantName } From cbeb0526177c657f0d3a901c1a0daf288634e892 Mon Sep 17 00:00:00 2001 From: ShocOne <62835948+ShocOne@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:17:34 +0100 Subject: [PATCH 9/9] Refactor Microsoft Graph integration code for improved authentication and URL handling --- go.mod | 6 ++--- jamfpro/jamfprointegration/interface.go | 4 ++-- msgraph/msgraphintegration/builders.go | 32 +++++++++++-------------- msgraph/msgraphintegration/interface.go | 4 ++-- msgraph/msgraphintegration/urls.go | 11 ++++++--- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index 1fc8cba..d8388a3 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ -module github.com/deploymenttheory/go-api-http-client-integration-jamfpro +module github.com/deploymenttheory/go-api-http-client-integrations -go 1.22.2 - -toolchain go1.22.3 +go 1.22.4 require ( github.com/deploymenttheory/go-api-http-client v0.1.43-0.20240603142132-2108676acbb3 diff --git a/jamfpro/jamfprointegration/interface.go b/jamfpro/jamfprointegration/interface.go index 0e7dcac..2c2f405 100644 --- a/jamfpro/jamfprointegration/interface.go +++ b/jamfpro/jamfprointegration/interface.go @@ -19,7 +19,7 @@ type Integration struct { // Info // TODO migrate strings -func (j *Integration) Domain() string { +func (j *Integration) GetFQDN() string { return j.BaseDomain } @@ -53,6 +53,6 @@ func (j *Integration) MarshalMultipartRequest(fields map[string]string, files ma // TODO migrate strings func (j *Integration) GetSessionCookies() ([]*http.Cookie, error) { - domain := j.Domain() + domain := j.GetFQDN() return j.getSessionCookies(domain) } diff --git a/msgraph/msgraphintegration/builders.go b/msgraph/msgraphintegration/builders.go index 334b740..a2197a8 100644 --- a/msgraph/msgraphintegration/builders.go +++ b/msgraph/msgraphintegration/builders.go @@ -12,7 +12,6 @@ import ( // Checks the token refresh status upon creation. // // Parameters: -// - msGraphBaseDomain: The base domain for the Microsoft Graph API. // - logger: A logger instance for logging purposes. // - bufferPeriod: The buffer period before token expiry to refresh the token. // - clientId: The client ID for OAuth2.0 authentication. @@ -22,9 +21,9 @@ import ( // Returns: // - *Integration: A pointer to the constructed Integration instance. // - error: Any error encountered during the token refresh check. -func BuildIntegrationWithOAuth(msGraphBaseDomain string, logger logger.Logger, bufferPeriod time.Duration, clientId string, clientSecret string, tenantID string) (*Integration, error) { - integration := Integration{ - BaseDomain: msGraphBaseDomain, +func BuildIntegrationWithOAuth(logger logger.Logger, bufferPeriod time.Duration, clientId string, clientSecret string, tenantID string) (*Integration, error) { + integration := &Integration{ + TenantID: tenantID, Logger: logger, AuthMethodDescriptor: "oauth2", } @@ -32,7 +31,7 @@ func BuildIntegrationWithOAuth(msGraphBaseDomain string, logger logger.Logger, b integration.BuildOAuth(clientId, clientSecret, bufferPeriod, tenantID) err := integration.CheckRefreshToken() - return &integration, err + return integration, err } // BuildIntegrationWithBasicAuth constructs an Integration instance using Basic Authentication. @@ -40,7 +39,6 @@ func BuildIntegrationWithOAuth(msGraphBaseDomain string, logger logger.Logger, b // Checks the token refresh status upon creation. // // Parameters: -// - msGraphBaseDomain: The base domain for the Microsoft Graph API. // - logger: A logger instance for logging purposes. // - bufferPeriod: The buffer period before token expiry to refresh the token. // - username: The username for basic authentication. @@ -50,9 +48,9 @@ func BuildIntegrationWithOAuth(msGraphBaseDomain string, logger logger.Logger, b // Returns: // - *Integration: A pointer to the constructed Integration instance. // - error: Any error encountered during the token refresh check. -func BuildIntegrationWithBasicAuth(msGraphBaseDomain string, logger logger.Logger, bufferPeriod time.Duration, username string, password string, tenantID string) (*Integration, error) { - integration := Integration{ - BaseDomain: msGraphBaseDomain, +func BuildIntegrationWithBasicAuth(logger logger.Logger, bufferPeriod time.Duration, username string, password string, tenantID string) (*Integration, error) { + integration := &Integration{ + TenantID: tenantID, Logger: logger, AuthMethodDescriptor: "basic", } @@ -60,7 +58,7 @@ func BuildIntegrationWithBasicAuth(msGraphBaseDomain string, logger logger.Logge integration.BuildBasicAuth(username, password, bufferPeriod, tenantID) err := integration.CheckRefreshToken() - return &integration, err + return integration, err } // BuildOAuth sets up the OAuth2.0 authentication method for the Integration instance. @@ -70,17 +68,16 @@ func BuildIntegrationWithBasicAuth(msGraphBaseDomain string, logger logger.Logge // - clientSecret: The client secret for OAuth2.0 authentication. // - bufferPeriod: The buffer period before token expiry to refresh the token. // - tenantID: The tenant ID for the Microsoft Graph API. -func (j *Integration) BuildOAuth(clientId string, clientSecret string, bufferPeriod time.Duration, tenantID string) { - authInterface := oauth{ +func (m *Integration) BuildOAuth(clientId string, clientSecret string, bufferPeriod time.Duration, tenantID string) { + authInterface := &oauth{ clientId: clientId, clientSecret: clientSecret, bufferPeriod: bufferPeriod, - baseDomain: j.BaseDomain, - Logger: j.Logger, + Logger: m.Logger, tenantID: tenantID, } - j.auth = &authInterface + m.auth = authInterface } // BuildBasicAuth sets up the basic authentication method for the Integration instance. @@ -91,14 +88,13 @@ func (j *Integration) BuildOAuth(clientId string, clientSecret string, bufferPer // - bufferPeriod: The buffer period before token expiry to refresh the token. // - tenantID: The tenant ID for the Microsoft Graph API. func (j *Integration) BuildBasicAuth(username string, password string, bufferPeriod time.Duration, tenantID string) { - authInterface := basicAuth{ + authInterface := &basicAuth{ username: username, password: password, bufferPeriod: bufferPeriod, logger: j.Logger, - baseDomain: j.BaseDomain, tenantID: tenantID, } - j.auth = &authInterface + j.auth = authInterface } diff --git a/msgraph/msgraphintegration/interface.go b/msgraph/msgraphintegration/interface.go index ab2ddda..a4a246f 100644 --- a/msgraph/msgraphintegration/interface.go +++ b/msgraph/msgraphintegration/interface.go @@ -18,9 +18,9 @@ type Integration struct { // Info -// TODO migrate strings +// Return the FQDN for Microsoft Graph func (m *Integration) GetFQDN() string { - return m.TenantName + return m.getFQDN } // TODO migrate strings diff --git a/msgraph/msgraphintegration/urls.go b/msgraph/msgraphintegration/urls.go index e7488c9..3f87b52 100644 --- a/msgraph/msgraphintegration/urls.go +++ b/msgraph/msgraphintegration/urls.go @@ -1,7 +1,12 @@ // apiintegrations/msgraph/urls.go package msgraphintegration -// GetTenantName returns the tenant name for the Microsoft Graph integration. -func (m *Integration) GetTenantName() string { - return m.TenantName +// GetTenantID returns the tenant ID for the Microsoft Graph integration. +func (m *Integration) GetTenantID() string { + return m.TenantID +} + +// getFQDN returns the fully qualified domain name for Microsoft Graph. +func (m *Integration) getFQDN() string { + return "https://graph.microsoft.com" }