diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 633b429..c3bd24b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,9 +13,5 @@ jobs: run: go build ./... - name: Vet run: go vet ./... - - name: Test - uses: cedrickring/golang-action@master - with: - args: make test - name: Secure run: make secure diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07a9137..f1ec025 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,16 +7,18 @@ jobs: goreleaser: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Unshallow - run: git fetch --prune --unshallow - - name: Set up Go - uses: actions/setup-go@v2 + - uses: actions/checkout@v3 with: - go-version: 1.14 - - name: Release - uses: docker://antonyurchenko/git-release:latest + fetch-depth: 0 + - run: git fetch --force --tags + - uses: actions/setup-go@v4 + with: + go-version: stable + - uses: goreleaser/goreleaser-action@v4 + with: + distribution: goreleaser + version: latest + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DRAFT_RELEASE: "false" diff --git a/LICENSE b/LICENSE index 389489f..1d748c8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2021 OneLogin, Inc. +Copyright (c) 2010-2023 OneLogin, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/config.yml b/config.yml index ef686a2..4160a9a 100644 --- a/config.yml +++ b/config.yml @@ -1 +1 @@ -version: "4.0.0" +version: "4.0.1" \ No newline at end of file diff --git a/docs/api.md b/docs/api.md index 4cdc8ca..b174f45 100644 --- a/docs/api.md +++ b/docs/api.md @@ -35,4 +35,4 @@ The `Authenticator` interface is used for handling authentication. It uses the ` The `Authenticator` is initialized with the `NewAuthenticator` function and the token is generated with the `GenerateToken` method within the `NewClient` function. If a request is unauthorized (HTTP 401), the token is refreshed using the `GenerateToken` method in the `sendRequest` function. -In summary, the API module simplifies the process of interacting with the OneLogin API by encapsulating the details of creating, sending, and processing HTTP requests. It uses environment variables for the API credentials and handles error scenarios such as unauthorized requests and token refresh. It forms the backbone of the OneLogin Go SDK, providing a streamlined interface for making API calls. \ No newline at end of file +In summary, the API module simplifies the process of interacting with the OneLogin API by encapsulating the details of creating, sending, and processing HTTP requests. It uses environment variables for the API credentials and handles error scenarios such as unauthorized requests and token refresh. It forms the backbone of the OneLogin Go SDK, providing a streamlined interface for making API calls. diff --git a/docs/authentication.md b/docs/authentication.md index 0f8acfe..bae648e 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -14,11 +14,11 @@ type Authenticator struct { ## NewAuthenticator Function -The `NewAuthenticator` function is used to create a new `Authenticator` instance. It does not require any arguments and it initializes a new Authenticator with an empty `accessToken`. +The `NewAuthenticator` function is used to create a new `Authenticator` instance. It requires the subdomain as an argument, and it initializes a new Authenticator with an empty `accessToken`. ```go -func NewAuthenticator() *Authenticator { - return &Authenticator{} +func NewAuthenticator(subdomain string) *Authenticator { + return &Authenticator{subdomain: subdomain} } ``` @@ -37,7 +37,7 @@ func (a *Authenticator) GenerateToken() error { The `RevokeToken` function is used to revoke an existing access token. It reads the `ONELOGIN_CLIENT_ID` and `ONELOGIN_CLIENT_SECRET` environment variables, creates a revocation request, sends it, and handles the response. If the revocation is successful, a confirmation message is printed. ```go -func (a *Authenticator) RevokeToken(token, domain *string) error { +func (a *Authenticator) RevokeToken(token *string) error { // implementation details } ``` diff --git a/docs/usage_examples.md b/docs/usage_examples.md index edddc1b..1a8171a 100644 --- a/docs/usage_examples.md +++ b/docs/usage_examples.md @@ -1,24 +1,106 @@ +1. **User model** + ```go package main import ( "fmt" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" +) + +func main() { + var ( + email string = "test@example.com" + username string = "testuser" + ) + + client, err := onelogin.NewOneloginSDK() + if err != nil { + fmt.Println(err) + } + + user, err := client.CreateUser(models.User{ + Email: &email, + Username: &username, + }) + if err != nil { + fmt.Println(err) + } + fmt.Printf("%+v\n", user) +} +``` - "github.com/onelogin/onelogin-go-sdk/internal/models" - "github.com/onelogin/onelogin-go-sdk/pkg/onelogin" +2. **Role model** + +```go +package main + +import ( + "fmt" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" ) func main() { - var testSSO = models.SSOOpenId{ClientID: "1234567890"} - var testConfig = models.ConfigurationOpenId{ - RedirectURI: "https://example.com", - LoginURL: "https://example.com/login", - OidcApplicationType: 1, - TokenEndpointAuthMethod: 1, - AccessTokenExpirationMinutes: 60, - RefreshTokenExpirationMinutes: 60, + var ( + name string = "Admin" + ) + + client, err := onelogin.NewOneloginSDK() + if err != nil { + fmt.Println(err) } + role, err := client.CreateRole(models.Role{ + Name: &name, + }) + if err != nil { + fmt.Println(err) + } + fmt.Printf("%+v\n", role) +} +``` + +3. **Event model** + +```go +package main + +import ( + "fmt" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" +) + +func main() { + client, err := onelogin.NewOneloginSDK() + if err != nil { + fmt.Println(err) + } + + eventQ := models.EventQuery{} + eventList, err := client.GetEvents(&eventQ) + if err != nil { + fmt.Println(err) + } + fmt.Printf("%+v\n", eventList) +} +``` + + +4. **App model** + +```go +package main + +import ( + "fmt" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" +) + +func main() { var ( connetorid int32 = 108419 name string = "Bow wow wow yippy yo yippy yay" @@ -29,24 +111,69 @@ func main() { if err != nil { fmt.Println(err) } - appQ := models.AppQuery{} - applist, err := client.GetApps(&appQ) + + app, err := client.CreateApp(models.App{ + ConnectorID: &connetorid, + Name: &name, + Description: &descr, + }) if err != nil { fmt.Println(err) } - fmt.Printf("%+v\n", applist) + fmt.Printf("%+v\n", app) +} +``` - appT, err := client.CreateApp(models.App{ - ConnectorID: &connetorid, - Name: &name, - Description: &descr, - SSO: testSSO, - Configuration: testConfig, - }) +5. **UserMappings model** + +```go +package main + +import ( + "fmt" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" +) + +func main() { + client, err := onelogin.NewOneloginSDK() + if err != nil { + fmt.Println(err) + } + + userMappingsQuery := models.UserMappingsQuery{} + userMappings, err := client.GetUserMappings(&userMappingsQuery) + if err != nil { + fmt.Println(err) + } + fmt.Printf("%+v\n", userMappings) +} +``` + +6. **AppRule model** + +```go +package main + +import ( + "fmt" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" +) + +func main() { + client, err := onelogin.NewOneloginSDK() + if err != nil { + fmt.Println(err) + } + + appRuleQuery := models.AppRuleQuery{} + appRules, err := client.GetAppRules(&appRuleQuery) if err != nil { fmt.Println(err) } - fmt.Printf("%+v\n", appT) + fmt.Printf("%+v\n", appRules) } +``` -``` \ No newline at end of file +Please note that these are basic examples and may not work as expected without proper setup and context. You may need to adjust them according to your needs. diff --git a/go.mod b/go.mod index f28dc8b..2ed8ad4 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/onelogin/onelogin-go-sdk +module github.com/onelogin/onelogin-go-sdk/v4 go 1.18 diff --git a/main.go b/main.go new file mode 100644 index 0000000..c624203 --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "os" + + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" +) + +func main() { + + // Set environment variables for OneLogin API credentials + os.Setenv("ONELOGIN_CLIENT_ID", "client_id") + os.Setenv("ONELOGIN_CLIENT_SECRET", "client_secret") + os.Setenv("ONELOGIN_SUBDOMAIN", "your-api-subdomain") + + // Create a user object with the specified attributes + UserTwo := models.User{Firstname: "Mikhail", Lastname: "Beaverton", Email: "mb@example.com"} + + // Create a user query object with the specified email + UserQueryOne := models.UserQuery{Email: &UserTwo.Email} + + // Create a new OneLogin SDK client + Client, err := onelogin.NewOneloginSDK() + if err != nil { + fmt.Println(err) + } + + // Create a new user with the specified attributes + Client.CreateUser(models.User{Firstname: "Jane", Lastname: "Pukalava", Email: "jp@example.com"}) + + // Create a new user with the specified attributes + Client.CreateUser(UserTwo) + + // Get a list of users that match the specified query + Client.GetUsers(&UserQueryOne) +} diff --git a/pkg/onelogin/api/client.go b/pkg/onelogin/api/client.go new file mode 100644 index 0000000..95d652f --- /dev/null +++ b/pkg/onelogin/api/client.go @@ -0,0 +1,191 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "strconv" + "time" + + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/authentication" + olerror "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/error" + mod "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" +) + +// Client represents the API client. +type Client struct { + HttpClient HTTPClient // HTTPClient interface for making HTTP requests + Auth *authentication.Authenticator // Authenticator for managing authentication + OLdomain string // OneLogin domain + Timeout time.Duration +} + +// HTTPClient is an interface that defines the Do method for making HTTP requests. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// Authenticator is an interface that defines the GetToken method for retrieving authentication tokens. +type Authenticator interface { + GetToken() (string, error) + NewAuthenticator() *authentication.Authenticator +} + +// NewClient creates a new instance of the API client. +func NewClient() (*Client, error) { + subdomain := os.Getenv("ONELOGIN_SUBDOMAIN") + old := fmt.Sprintf("https://%s.onelogin.com", subdomain) + authenticator := authentication.NewAuthenticator(subdomain) + timeoutStr := os.Getenv("ONELOGIN_TIMEOUT") + timeout, err := strconv.Atoi(timeoutStr) + if err != nil || timeout <= 0 { + timeout = 10 + } + timeoutDuration := time.Second * time.Duration(timeout) + + err = authenticator.GenerateToken() + if err != nil { + return nil, err + } + return &Client{ + HttpClient: &http.Client{ + Timeout: timeoutDuration, + }, + Auth: authenticator, + OLdomain: old, + }, nil +} + +// newRequest creates a new HTTP request with the specified method, path, query parameters, and request body. +func (c *Client) newRequest(method string, path *string, queryParams mod.Queryable, body io.Reader) (*http.Request, error) { + + p, err := utl.AddQueryToPath(*path, queryParams) + if err != nil { + return nil, err + } + log.Println("Path:", p) + // Parse the OneLogin domain and path + u, err := url.Parse(c.OLdomain + p) + if err != nil { + return nil, err + } + + // Create a new HTTP request + req, err := http.NewRequest(method, u.String(), body) + if err != nil { + return nil, err + } + + // Get authentication token + log.Println("Getting authentication token...") + tk, err := c.Auth.GetToken() + if err != nil { + log.Println("Error getting authentication token:", err) + return nil, olerror.NewAuthenticationError("Access Token Retrieval Error") + } + log.Println("Authentication token retrieved successfully.") + + // Set request headers + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tk)) + req.Header.Set("Content-Type", "application/json") + + return req, nil +} + +// Get sends a GET request to the specified path with the given query parameters. +func (c *Client) Get(path *string, queryParams mod.Queryable) (*http.Response, error) { + req, err := c.newRequest(http.MethodGet, path, queryParams, http.NoBody) + if err != nil { + return nil, err + } + + return c.sendRequest(req) +} + +// Delete sends a DELETE request to the specified path with the given query parameters. +func (c *Client) Delete(path *string) (*http.Response, error) { + req, err := c.newRequest(http.MethodDelete, path, nil, http.NoBody) + if err != nil { + return nil, err + } + + return c.sendRequest(req) +} + +// Delete sends a DELETE request to the specified path with the given query parameters and request body. +func (c *Client) DeleteWithBody(path *string, body interface{}) (*http.Response, error) { + // Convert request body to JSON + jsonBody, err := json.Marshal(body) + if err != nil { + return nil, err + } + req, err := c.newRequest(http.MethodDelete, path, nil, bytes.NewReader(jsonBody)) + if err != nil { + return nil, err + } + + return c.sendRequest(req) +} + +// Post sends a POST request to the specified path with the given query parameters and request body. +func (c *Client) Post(path *string, body interface{}) (*http.Response, error) { + // Convert request body to JSON + jsonBody, err := json.Marshal(body) + if err != nil { + return nil, err + } + + req, err := c.newRequest(http.MethodPost, path, nil, bytes.NewReader(jsonBody)) + if err != nil { + return nil, err + } + + return c.sendRequest(req) +} + +// Put sends a PUT request to the specified path with the given query parameters and request body. +func (c *Client) Put(path *string, body interface{}) (*http.Response, error) { + // Convert request body to JSON + jsonBody, err := json.Marshal(body) + if err != nil { + return nil, err + } + + req, err := c.newRequest(http.MethodPut, path, nil, bytes.NewReader(jsonBody)) + if err != nil { + return nil, err + } + + return c.sendRequest(req) +} + +// sendRequest sends the specified HTTP request and returns the HTTP response. +func (c *Client) sendRequest(req *http.Request) (*http.Response, error) { + resp, err := c.HttpClient.Do(req) + if err != nil { + return nil, err + } + + // Check for API errors + if resp.StatusCode == http.StatusUnauthorized { + // Regenerate the token and reattempt the request + err := c.Auth.GenerateToken() + if err != nil { + return nil, olerror.NewAuthenticationError("Failed to refresh access token") + } + + // Retry the request + resp, err = c.HttpClient.Do(req) + if err != nil { + return nil, err + } + } + + return resp, nil +} diff --git a/pkg/onelogin/api_authorizations.go b/pkg/onelogin/api_authorizations.go index 41f6257..0523dca 100644 --- a/pkg/onelogin/api_authorizations.go +++ b/pkg/onelogin/api_authorizations.go @@ -1,8 +1,8 @@ package onelogin import ( - mod "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + mod "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/apps.go b/pkg/onelogin/apps.go index 5fd4cbb..7c675dc 100644 --- a/pkg/onelogin/apps.go +++ b/pkg/onelogin/apps.go @@ -1,8 +1,8 @@ package onelogin import ( - mod "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + mod "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/authentication/authenticator.go b/pkg/onelogin/authentication/authenticator.go new file mode 100644 index 0000000..a735475 --- /dev/null +++ b/pkg/onelogin/authentication/authenticator.go @@ -0,0 +1,153 @@ +package authentication + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "strings" + + olError "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/error" +) + +const ( + TkPath string = "/auth/oauth2/v2/token" + RevokePath string = "/auth/oauth2/revoke" +) + +type Authenticator struct { + accessToken string + subdomain string +} + +func NewAuthenticator(subdomain string) *Authenticator { + return &Authenticator{subdomain: subdomain} +} + +func (a *Authenticator) GenerateToken() error { + // Read & Check environment variables + clientID := os.Getenv("ONELOGIN_CLIENT_ID") + if len(clientID) == 0 { + return olError.NewAuthenticationError("Missing ONELOGIN_CLIENT_ID Env Variable") + } + //fmt.Println("clientID", clientID) + clientSecret := os.Getenv("ONELOGIN_CLIENT_SECRET") + if len(clientSecret) == 0 { + return olError.NewAuthenticationError("Missing ONELOGIN_CLIENT_SECRET Env Variable") + } + + // Construct the authentication URL + authURL := fmt.Sprintf("https://%s.onelogin.com%s", a.subdomain, TkPath) + + // Create authentication request payload + data := map[string]string{ + "grant_type": "client_credentials", + } + + // Convert payload to JSON + jsonData, err := json.Marshal(data) + if err != nil { + return olError.NewSerializationError("Unable to convert payload to JSON") + } + + // Create HTTP request + req, err := http.NewRequest(http.MethodPost, authURL, strings.NewReader(string(jsonData))) + if err != nil { + return olError.NewRequestError("Failed to create authentication request") + } + + // Add authorization header with base64-encoded credentials + encodedCredentials := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", clientID, clientSecret))) + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCredentials)) + req.Header.Add("Content-Type", "application/json") + + // Send the HTTP request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return olError.NewRequestError("Failed to send authentication request") + } + + // Parse the authentication response + var result map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return olError.NewSerializationError("Failed to read authentication response") + } + + // Check if authentication failed + if resp.StatusCode != http.StatusOK { + return olError.NewAuthenticationError("Authentication failed") + } + + // Extract access token from the response + accessToken, ok := result["access_token"].(string) + if !ok { + return olError.NewAuthenticationError("Authentication Failed at Endpoint") + } + // Store access token + a.accessToken = accessToken + + return nil +} + +func (a *Authenticator) RevokeToken(token *string) error { + // Read environment variables + clientID := os.Getenv("ONELOGIN_CLIENT_ID") + clientSecret := os.Getenv("ONELOGIN_CLIENT_SECRET") + + // Check if required environment variables are missing + if clientID == "" || clientSecret == "" { + return errors.New("missing client ID, client secret, or subdomain") + } + + // Construct the revoke URL + revokeURL := fmt.Sprintf("%s.onelogin.com%s", a.subdomain, RevokePath) + + // Create revoke request payload + data := struct { + AccessToken string `json:"access_token"` + }{ + AccessToken: *token, + } + + // Convert payload to JSON + jsonData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("failed to create revocation request: %w", err) + } + + // Create HTTP request + req, err := http.NewRequest("POST", revokeURL, strings.NewReader(string(jsonData))) + if err != nil { + return fmt.Errorf("failed to create revocation request: %w", err) + } + + // Add authorization header with base64-encoded credentials + encodedCredentials := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", clientID, clientSecret))) + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", encodedCredentials)) + req.Header.Add("Content-Type", "application/json") + + // Send the HTTP request + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to revoke: %w", err) + } + + // Check if revocation failed + if resp.StatusCode != http.StatusOK { + return olError.NewAuthenticationError("Revocation failed") + } + + // Success condition feedback + fmt.Println("Revocation successful") + + return nil +} + +func (a *Authenticator) GetToken() (string, error) { + return a.accessToken, nil +} diff --git a/pkg/onelogin/error/api_error.go b/pkg/onelogin/error/api_error.go new file mode 100644 index 0000000..bd77edb --- /dev/null +++ b/pkg/onelogin/error/api_error.go @@ -0,0 +1,19 @@ +package error + +import "fmt" + +type APIError struct { + Message string + Code int +} + +func (e *APIError) Error() string { + return fmt.Sprintf("API error: %s", e.Message) +} + +func NewAPIError(message string, code int) *APIError { + return &APIError{ + Message: message, + Code: code, + } +} diff --git a/pkg/onelogin/error/authentication_error.go b/pkg/onelogin/error/authentication_error.go new file mode 100644 index 0000000..12ad5fe --- /dev/null +++ b/pkg/onelogin/error/authentication_error.go @@ -0,0 +1,19 @@ +package error + +import ( + "fmt" +) + +type AuthenticationError struct { + Message string +} + +func (e *AuthenticationError) Error() string { + return fmt.Sprintf("Authentication error: %s", e.Message) +} + +func NewAuthenticationError(message string) *AuthenticationError { + return &AuthenticationError{ + Message: message, + } +} diff --git a/pkg/onelogin/error/request_error.go b/pkg/onelogin/error/request_error.go new file mode 100644 index 0000000..aa6f783 --- /dev/null +++ b/pkg/onelogin/error/request_error.go @@ -0,0 +1,19 @@ +package error + +import ( + "fmt" +) + +type RequestError struct { + Message string +} + +func (e RequestError) Error() string { + return fmt.Sprintf("Request error: %s", e.Message) +} + +func NewRequestError(message string) *RequestError { + return &RequestError{ + Message: message, + } +} diff --git a/pkg/onelogin/error/sdk_error.go b/pkg/onelogin/error/sdk_error.go new file mode 100644 index 0000000..1e4510d --- /dev/null +++ b/pkg/onelogin/error/sdk_error.go @@ -0,0 +1,17 @@ +package error + +import "fmt" + +type SDKError struct { + Message string +} + +func (e SDKError) Error() string { + return fmt.Sprintf("SDK error: %s", e.Message) +} + +func NewSDKError(message string) error { + return SDKError{ + Message: message, + } +} diff --git a/pkg/onelogin/error/serialization_error.go b/pkg/onelogin/error/serialization_error.go new file mode 100644 index 0000000..5c8320b --- /dev/null +++ b/pkg/onelogin/error/serialization_error.go @@ -0,0 +1,19 @@ +package error + +import ( + "fmt" +) + +type SerializationError struct { + Message string +} + +func (e SerializationError) Error() string { + return fmt.Sprintf("Serialization error: %s", e.Message) +} + +func NewSerializationError(message string) error { + return SerializationError{ + Message: message, + } +} diff --git a/pkg/onelogin/groups.go b/pkg/onelogin/groups.go index 97e7ed4..ccfb998 100644 --- a/pkg/onelogin/groups.go +++ b/pkg/onelogin/groups.go @@ -1,6 +1,6 @@ package onelogin -import utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" +import utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" const ( GroupsPath = "api/1/groups" diff --git a/pkg/onelogin/mfas.go b/pkg/onelogin/mfas.go index 7e69225..f4c1c70 100644 --- a/pkg/onelogin/mfas.go +++ b/pkg/onelogin/mfas.go @@ -1,8 +1,8 @@ package onelogin import ( - "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/models/app.go b/pkg/onelogin/models/app.go new file mode 100644 index 0000000..9886c09 --- /dev/null +++ b/pkg/onelogin/models/app.go @@ -0,0 +1,139 @@ +package models + +type App struct { + ID *int32 `json:"id,omitempty"` + ConnectorID *int32 `json:"connector_id"` + Name *string `json:"name"` + Description *string `json:"description,omitempty"` + Notes *string `json:"notes,omitempty"` + PolicyID *int `json:"policy_id,omitempty"` + BrandID *int `json:"brand_id,omitempty"` + IconURL *string `json:"icon_url,omitempty"` + Visible *bool `json:"visible,omitempty"` + AuthMethod *int `json:"auth_method,omitempty"` + TabID *int `json:"tab_id,omitempty"` + CreatedAt *string `json:"created_at,omitempty"` + UpdatedAt *string `json:"updated_at,omitempty"` + RoleIDs *[]int `json:"role_ids,omitempty"` + AllowAssumedSignin *bool `json:"allow_assumed_signin,omitempty"` + Provisioning *Provisioning `json:"provisioning,omitempty"` + SSO interface{} `json:"sso,omitempty"` + Configuration interface{} `json:"configuration,omitempty"` + Parameters *map[string]Parameter `json:"parameters,omitempty"` + EnforcementPoint *EnforcementPoint `json:"enforcement_point,omitempty"` +} + +type Provisioning struct { + Enabled bool `json:"enabled"` +} + +type SSO interface { + ValidateSSO() error +} + +type SSOOpenId struct { + ClientID string `json:"client_id"` +} + +type SSOSAML struct { + MetadataURL string `json:"metadata_url"` + AcsURL string `json:"acs_url"` + SlsURL string `json:"sls_url"` + Issuer string `json:"issuer"` + Certificate Certificate `json:"certificate"` +} + +type Certificate struct { + ID int `json:"id"` + Name string `json:"name"` + Value string `json:"value"` +} + +type ConfigurationOpenId struct { + RedirectURI string `json:"redirect_uri"` + LoginURL string `json:"login_url"` + OidcApplicationType int `json:"oidc_application_type"` + TokenEndpointAuthMethod int `json:"token_endpoint_auth_method"` + AccessTokenExpirationMinutes int `json:"access_token_expiration_minutes"` + RefreshTokenExpirationMinutes int `json:"refresh_token_expiration_minutes"` +} + +type ConfigurationSAML struct { + ProviderArn interface{} `json:"provider_arn"` + SignatureAlgorithm string `json:"signature_algorithm"` + CertificateID int `json:"certificate_id"` +} + +type Parameter struct { + Values interface{} `json:"values,omitempty"` + UserAttributeMappings interface{} `json:"user_attribute_mappings,omitempty"` + ProvisionedEntitlements bool `json:"provisioned_entitlements,omitempty"` + SkipIfBlank bool `json:"skip_if_blank,omitempty"` + ID int `json:"id,omitempty"` + DefaultValues interface{} `json:"default_values"` + AttributesTransformations interface{} `json:"attributes_transformations,omitempty"` + Label string `json:"label,omitempty"` + UserAttributeMacros interface{} `json:"user_attribute_macros,omitempty"` + IncludeInSamlAssertion bool `json:"include_in_saml_assertion,omitempty"` +} + +type EnforcementPoint struct { + RequireSitewideAuthentication bool `json:"require_sitewide_authentication"` + Conditions *Conditions `json:"conditions,omitempty"` + SessionExpiryFixed Duration `json:"session_expiry_fixed"` + SessionExpiryInactivity Duration `json:"session_expiry_inactivity"` + Permissions string `json:"permissions"` + Token string `json:"token,omitempty"` + Target string `json:"target"` + Resources []Resource `json:"resources"` + ContextRoot string `json:"context_root"` + UseTargetHostHeader bool `json:"use_target_host_header"` + Vhost string `json:"vhost"` + LandingPage string `json:"landing_page"` + CaseSensitive bool `json:"case_sensitive"` +} + +type Conditions struct { + Type string `json:"type"` + Roles []string `json:"roles"` +} + +type Duration struct { + Value int `json:"value"` + Unit int `json:"unit"` +} + +type Resource struct { + Path string `json:"path"` + RequireAuth string `json:"require_authentication"` + Permissions string `json:"permissions"` + Conditions *string `json:"conditions,omitempty"` + IsPathRegex *bool `json:"is_path_regex,omitempty"` + ResourceID int `json:"resource_id,omitempty"` +} + +const ( + UnitSeconds = 0 + UnitMinutes = 1 + UnitHours = 2 +) + +type AppQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` + Name *string `json:"name,omitempty"` + ConnectorID *int `json:"connector_id,omitempty"` + AuthMethod *int `json:"auth_method,omitempty"` +} + +func (q *AppQuery) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "limit": validateString, + "page": validateString, + "cursor": validateString, + "name": validateString, + "connector_id": validateInt, + "auth_method": validateInt, + } +} diff --git a/pkg/onelogin/models/app_rule.go b/pkg/onelogin/models/app_rule.go new file mode 100644 index 0000000..9ab8e41 --- /dev/null +++ b/pkg/onelogin/models/app_rule.go @@ -0,0 +1,49 @@ +package models + +type Condition struct { + Source string `json:"source"` + Operator string `json:"operator"` + Value string `json:"value"` +} + +type Action struct { + Action string `json:"action"` + Value []string `json:"value,omitempty"` + Expression string `json:"expression,omitempty"` + Scriplet string `json:"scriplet,omitempty"` + Macro string `json:"macro,omitempty"` +} + +type AppRule struct { + AppID int `json:"app_id"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Match string `json:"match"` + Position int `json:"position,omitempty"` + Conditions []Condition `json:"conditions"` + Actions []Action `json:"actions"` +} + +type AppRuleQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` + Enabled bool `json:"enabled,omitempty"` + HasCondition *string `json:"has_condition,omitempty"` + HasConditionType *string `json:"has_condition_type,omitempty"` + HasAction *string `json:"has_action,omitempty"` + HasActionType *string `json:"has_action_type,omitempty"` +} + +func (q *AppRuleQuery) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "limit": validateString, + "page": validateString, + "cursor": validateString, + "enabled": validateBool, + "has_condition": validateString, + "has_condition_type": validateString, + "has_action": validateString, + "has_action_type": validateString, + } +} diff --git a/pkg/onelogin/models/auth_server.go b/pkg/onelogin/models/auth_server.go new file mode 100644 index 0000000..c4eede6 --- /dev/null +++ b/pkg/onelogin/models/auth_server.go @@ -0,0 +1,90 @@ +package models + +type AuthServerQuery struct { + Name string `json:"name,omitempty"` + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` +} + +type AuthServer struct { + ID *int32 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Configuration *AuthServerConfiguration `json:"configuration,omitempty"` +} + +type AuthServerConfiguration struct { + ResourceIdentifier *string `json:"resource_identifier,omitempty"` + Audiences []string `json:"audiences,omitempty"` + AccessTokenExpirationMinutes *int32 `json:"access_token_expiration_minutes,omitempty"` + RefreshTokenExpirationMinutes *int32 `json:"refresh_token_expiration_minutes,omitempty"` +} + +type ClientAppsQuery struct { + ID int `json:"id,omitempty"` +} + +type ClientApp struct { + ID *int32 `json:"app_id,omitempty"` + AuthServerID *int32 `json:"auth_server_id,omitempty"` + APIAuthID *int32 `json:"api_auth_id,omitempty"` + Name *string `json:"name,omitempty"` + Scopes []Scope `json:"scopes,omitempty"` + ScopeIDs []int32 `json:"scope_ids,omitempty"` +} + +type ScopesQuery struct { + ID int `json:"id,omitempty"` +} + +type Scope struct { + ID *int32 `json:"id,omitempty"` + AuthServerID *int32 `json:"auth_server_id,omitempty"` + Value *string `json:"value,omitempty"` + Description *string `json:"description,omitempty"` +} + +type AccessTokenClaimsQuery struct { + ID int `json:"id,omitempty"` +} + +type AccessTokenClaim struct { + ID *int32 `json:"id,omitempty"` + AuthServerID *int32 `json:"auth_server_id,omitempty"` + Label *string `json:"label,omitempty"` + UserAttributeMappings *string `json:"user_attribute_mappings,omitempty"` + UserAttributeMacros *string `json:"user_attribute_macros,omitempty"` + AttributeTransformations *string `json:"attribute_transformations,omitempty"` + SkipIfBlank *bool `json:"skip_if_blank,omitempty"` + Values []string `json:"values,omitempty"` + DefaultValues *string `json:"default_values,omitempty"` + ProvisionedEntitlements *bool `json:"provisioned_entitlements,omitempty"` +} + +func (q *AuthServerQuery) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "name": validateString, + "limit": validateString, + "page": validateString, + "cursor": validateString, + } +} + +func (q *ClientAppsQuery) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "auth_server_id": validateString, + } +} + +func (q *ScopesQuery) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "auth_server_id": validateString, + } +} + +func (q *AccessTokenClaimsQuery) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "auth_server_id": validateString, + } +} diff --git a/pkg/onelogin/models/group.go b/pkg/onelogin/models/group.go new file mode 100644 index 0000000..2c43c17 --- /dev/null +++ b/pkg/onelogin/models/group.go @@ -0,0 +1,7 @@ +package models + +type Group struct { + ID int `json:"id"` + Name string `json:"name"` + Reference *string `json:"reference"` +} diff --git a/pkg/onelogin/models/mfa.go b/pkg/onelogin/models/mfa.go new file mode 100644 index 0000000..c6c2e43 --- /dev/null +++ b/pkg/onelogin/models/mfa.go @@ -0,0 +1,37 @@ +package models + +type GenerateSAMLTokenRequest struct { + UsernameOrEmail string `json:"username_or_email"` + Password string `json:"password"` + AppID string `json:"app_id"` + Subdomain string `json:"subdomain"` + IPAddress string `json:"ip_address,omitempty"` +} + +type VerifyMFATokenRequest struct { + AppID string `json:"app_id"` + DeviceID string `json:"device_id"` + StateToken string `json:"state_token"` + OTPToken string `json:"otp_token,omitempty"` + DoNotNotify bool `json:"do_not_notify,omitempty"` +} + +type EnrollFactorRequest struct { + FactorID int `json:"factor_id"` + DisplayName string `json:"display_name"` + ExpiresIn string `json:"expires_in,omitempty"` + Verified bool `json:"verified,omitempty"` + RedirectTo string `json:"redirect_to,omitempty"` + CustomMessage string `json:"custom_message,omitempty"` +} +type ActivateFactorRequest struct { + DeviceID string `json:"device_id"` + ExpiresIn string `json:"expires_in,omitempty"` + RedirectTo string `json:"redirect_to,omitempty"` + CustomMessage string `json:"custom_message,omitempty"` +} + +type GenerateMFATokenRequest struct { + ExpiresIn string `json:"expires_in,omitempty"` + Reusable bool `json:"reusable,omitempty"` +} diff --git a/pkg/onelogin/models/privilege.go b/pkg/onelogin/models/privilege.go new file mode 100644 index 0000000..a6960b7 --- /dev/null +++ b/pkg/onelogin/models/privilege.go @@ -0,0 +1,39 @@ +package models + +// PrivilegeQuery represents available query parameters +type PrivilegeQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` +} + +// Privilege represents the Role resource in OneLogin +type Privilege struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Privilege *PrivilegeData `json:"privilege,omitempty"` + UserIDs []int `json:"user_ids,omitempty"` + RoleIDs []int `json:"role_ids,omitempty"` +} + +// PrivilegeData represents the group of statements and statement versions pertinent to a privilege +type PrivilegeData struct { + Version *string `json:"version,omitempty"` + Statement []StatementData `json:"Statement"` +} + +// StatementData represents the actions and scope of a given privilege +type StatementData struct { + Effect *string `json:"Effect,omitempty"` + Action []string `json:"Action"` + Scope []string `json:"Scope"` +} + +func (p *Privilege) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "limit": validateString, + "page": validateString, + "cursor": validateString, + } +} diff --git a/pkg/onelogin/models/role.go b/pkg/onelogin/models/role.go new file mode 100644 index 0000000..533faaa --- /dev/null +++ b/pkg/onelogin/models/role.go @@ -0,0 +1,25 @@ +package models + +// RoleQuery represents available query parameters +type RoleQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` +} + +// Role represents the Role resource in OneLogin +type Role struct { + ID *int32 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Admins []int32 `json:"admins,omitempty"` + Apps []int32 `json:"apps,omitempty"` + Users []int32 `json:"users,omitempty"` +} + +func (r *Role) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "limit": validateString, + "page": validateString, + "cursor": validateString, + } +} diff --git a/pkg/onelogin/models/smart_hook.go b/pkg/onelogin/models/smart_hook.go new file mode 100644 index 0000000..6f04246 --- /dev/null +++ b/pkg/onelogin/models/smart_hook.go @@ -0,0 +1,87 @@ +package models + +import ( + "time" +) + +const ( + TypePreAuthentication string = "pre-authentication" + TypeUserMigration string = "user-migration" +) + +const ( + ContextPreAuthentication1_0_0 string = "1.0.0" + ContextPreAuthentication1_1_0 string = "1.1.0" + + ContextUserMigration1_0_0 string = "1.0.0" +) + +const ( + StatusReady string = "ready" + StatusCreateQueued string = "create-queued" + StatusCreateRunning string = "create-running" + StatusCreateFailed string = "create-failed" + StatusUpdateQueued string = "update-queued" + StatusUpdateRunning string = "update-running" + StatusUpdateFailed string = "update-failed" +) + +// SmartHookQuery represents available query parameters +type SmartHookQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` + Type string `json:"type,omitempty"` +} + +// SmartHook represents a OneLogin SmartHook with associated resource data +type SmartHook struct { + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Disabled *bool `json:"disabled,omitempty"` + Timeout *int32 `json:"timeout,omitempty"` + EnvVars []EnvVar `json:"env_vars"` + Runtime *string `json:"runtime,omitempty"` + ContextVersion *string `json:"context_version,omitempty"` + Retries *int32 `json:"retries,omitempty"` + Options *Options `json:"options,omitempty"` + Packages map[string]string `json:"packages"` + Function *string `json:"function,omitempty"` + Status *string `json:"status,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Conditions []Condition `json:"conditions,omitempty"` +} + +// SmartHookOptions represents the options to be associated with a SmartHook +type Options struct { + RiskEnabled *bool `json:"risk_enabled,omitempty"` + MFADeviceInfoEnabled *bool `json:"mfa_device_info_enabled,omitempty"` + LocationEnabled *bool `json:"location_enabled,omitempty"` +} + +// SmartHookEnvVarQuery represents available query parameters +type SmartHookEnvVarQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` + Type string `json:"type,omitempty"` +} + +// EnvVar represents an Environment Variable to be associated with a SmartHook +type EnvVar struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Value *string `json:"value,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (s *SmartHook) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "limit": validateString, + "page": validateString, + "cursor": validateString, + "type": validateString, + } +} diff --git a/pkg/onelogin/models/user.go b/pkg/onelogin/models/user.go new file mode 100644 index 0000000..1023655 --- /dev/null +++ b/pkg/onelogin/models/user.go @@ -0,0 +1,118 @@ +package models + +import "time" + +const ( + StateUnapproved int32 = iota + StateApproved + StateRejected + StateUnlicensed +) + +const ( + StatusUnActivated int32 = iota + StatusActive // Only users assigned this status can log in to OneLogin. + StatusSuspended + StatusLocked + StatusPasswordExpired + StatusAwaitingPasswordReset // The user is required to reset their password. + statusUnused6 // There is not user status with a value of 6. + StatusPasswordPending // The user has not yet set their password. + StatusSecurityQuestionsRequired // The user has not yet set their security questions. +) + +// UserQuery represents available query parameters +type UserQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` + CreatedSince *time.Time `json:"created_since,omitempty"` + CreatedUntil *time.Time `json:"created_until,omitempty"` + UpdatedSince *time.Time `json:"updated_since,omitempty"` + UpdatedUntil *time.Time `json:"updated_until,omitempty"` + LastLoginSince *time.Time `json:"last_login_since,omitempty"` + LastLoginUntil *time.Time `json:"last_login_until,omitempty"` + Firstname *string `json:"firstname,omitempty"` + Lastname *string `json:"lastname,omitempty"` + Email *string `json:"email,omitempty"` + Username *string `json:"username,omitempty"` + Samaccountname *string `json:"samaccountname,omitempty"` + DirectoryID *string `json:"directory_id,omitempty"` + ExternalID *string `json:"external_id,omitempty"` + AppID *string `json:"app_id,omitempty"` + UserIDs *string `json:"user_ids,omitempty"` + Fields *string `json:"fields,omitempty"` +} + +// User represents a OneLogin User +type User struct { + Firstname string `json:"firstname,omitempty"` + Lastname string `json:"lastname,omitempty"` + Username string `json:"username,omitempty"` + Email string `json:"email,omitempty"` + DistinguishedName string `json:"distinguished_name,omitempty"` + Samaccountname string `json:"samaccountname,omitempty"` + UserPrincipalName string `json:"userprincipalname,omitempty"` + MemberOf string `json:"member_of,omitempty"` + Phone string `json:"phone,omitempty"` + Password string `json:"password,omitempty"` + PasswordConfirmation string `json:"password_confirmation,omitempty"` + PasswordAlgorithm string `json:"password_algorithm,omitempty"` + Salt string `json:"salt,omitempty"` + Title string `json:"title,omitempty"` + Company string `json:"company,omitempty"` + Department string `json:"department,omitempty"` + Comment string `json:"comment,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + ActivatedAt time.Time `json:"activated_at,omitempty"` + LastLogin time.Time `json:"last_login,omitempty"` + PasswordChangedAt time.Time `json:"password_changed_at,omitempty"` + LockedUntil time.Time `json:"locked_until,omitempty"` + InvitationSentAt time.Time `json:"invitation_sent_at,omitempty"` + State int32 `json:"state,omitempty"` + Status int32 `json:"status,omitempty"` + InvalidLoginAttempts int32 `json:"invalid_login_attempts,omitempty"` + GroupID int32 `json:"group_id,omitempty"` + DirectoryID int32 `json:"directory_id,omitempty"` + TrustedIDPID int32 `json:"trusted_idp_id,omitempty"` + ManagerADID int32 `json:"manager_ad_id,omitempty"` + ManagerUserID int32 `json:"manager_user_id,omitempty"` + ExternalID int32 `json:"external_id,omitempty"` + ID int32 `json:"id,omitempty"` + CustomAttributes map[string]interface{} `json:"custom_attributes,omitempty"` +} + +func (q *UserQuery) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "limit": validateString, + "page": validateString, + "cursor": validateString, + "createdSince": validateTime, + "createdUntil": validateTime, + "updatedSince": validateTime, + "updatedUntil": validateTime, + "lastLoginSince": validateTime, + "lastLoginUntil": validateTime, + "firstname": validateString, + "lastname": validateString, + "email": validateString, + "username": validateString, + "samaccountname": validateString, + "directoryID": validateString, + "externalID": validateString, + "appID": validateString, + "userIDs": validateString, + "fields": validateString, + } +} + +// UserApp is the contract for a users app. +type UserApp struct { + ID *int32 `json:"id,omitempty"` + IconURL *string `json:"icon_url,omitempty"` + LoginID *int32 `json:"login_id,omitempty"` + ProvisioningStatus *string `json:"provisioning_status,omitempty"` + ProvisioningState *string `json:"provisioning_state,omitempty"` + ProvisioningEnabled *bool `json:"provisioning_enabled,omitempty"` +} diff --git a/pkg/onelogin/models/user_mapping.go b/pkg/onelogin/models/user_mapping.go new file mode 100644 index 0000000..7df23c3 --- /dev/null +++ b/pkg/onelogin/models/user_mapping.go @@ -0,0 +1,50 @@ +package models + +// UserMappingsQuery represents available query parameters for mappings +type UserMappingsQuery struct { + Limit string `json:"limit,omitempty"` + Page string `json:"page,omitempty"` + Cursor string `json:"cursor,omitempty"` + HasCondition string `json:"has_condition,omitempty"` + HasConditionType string `json:"has_condition_type,omitempty"` + HasAction string `json:"has_action,omitempty"` + HasActionType string `json:"has_action_type,omitempty"` + Enabled string `json:"enabled,omitempty"` +} + +// UserMapping is the contract for User Mappings. +type UserMapping struct { + ID *int32 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Match *string `json:"match,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Position *int32 `json:"position,omitempty"` + Conditions []UserMappingConditions `json:"conditions"` + Actions []UserMappingActions `json:"actions"` +} + +// UserMappingConditions is the contract for User Mapping Conditions. +type UserMappingConditions struct { + Source *string `json:"source,omitempty"` + Operator *string `json:"operator,omitempty"` + Value *string `json:"value,omitempty"` +} + +// UserMappingActions is the contract for User Mapping Actions. +type UserMappingActions struct { + Action *string `json:"action,omitempty"` + Value []string `json:"value,omitempty"` +} + +func (u *UserMapping) GetKeyValidators() map[string]func(interface{}) bool { + return map[string]func(interface{}) bool{ + "limit": validateString, + "page": validateString, + "cursor": validateString, + "has_condition": validateString, + "has_condition_id": validateString, + "has_action": validateString, + "has_action_id": validateString, + "enabled": validateBool, + } +} diff --git a/pkg/onelogin/models/validation.go b/pkg/onelogin/models/validation.go new file mode 100644 index 0000000..0f76f77 --- /dev/null +++ b/pkg/onelogin/models/validation.go @@ -0,0 +1,57 @@ +package models + +import ( + "time" +) + +type Queryable interface { + GetKeyValidators() map[string]func(interface{}) bool +} + +// validateString checks if the provided value is a string. +func validateString(val interface{}) bool { + switch v := val.(type) { + case string: + return true + case *string: + return v != nil + default: + return false + } +} + +// validateTime checks if the provided value is a time.Time. +func validateTime(val interface{}) bool { + switch v := val.(type) { + case time.Time: + return true + case *time.Time: + return v != nil + default: + return false + } +} + +// validateInt checks if the provided value is an int. +func validateInt(val interface{}) bool { + switch v := val.(type) { + case int: + return true + case *int: + return v != nil + default: + return false + } +} + +// validateBool checks if the provided value is a bool. +func validateBool(val interface{}) bool { + switch v := val.(type) { + case bool: + return true + case *bool: + return v != nil + default: + return false + } +} diff --git a/pkg/onelogin/onelogin.go b/pkg/onelogin/onelogin.go index 5b4dd5d..0a69f61 100644 --- a/pkg/onelogin/onelogin.go +++ b/pkg/onelogin/onelogin.go @@ -1,8 +1,8 @@ package onelogin import ( - "github.com/onelogin/onelogin-go-sdk/internal/api" - olerror "github.com/onelogin/onelogin-go-sdk/internal/error" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/api" + olerror "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/error" ) // OneloginSDK represents the Onelogin SDK. diff --git a/pkg/onelogin/privileges.go b/pkg/onelogin/privileges.go index d8ae218..7e44e8c 100644 --- a/pkg/onelogin/privileges.go +++ b/pkg/onelogin/privileges.go @@ -1,8 +1,8 @@ package onelogin import ( - "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/roles.go b/pkg/onelogin/roles.go index 05d5d1c..d59e02f 100644 --- a/pkg/onelogin/roles.go +++ b/pkg/onelogin/roles.go @@ -1,8 +1,8 @@ package onelogin import ( - mod "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + mod "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) var ( @@ -23,7 +23,10 @@ func (sdk *OneloginSDK) CreateRole(role *mod.Role) (interface{}, error) { // was ListRoles func (sdk *OneloginSDK) GetRoles(queryParams mod.Queryable) (interface{}, error) { - p := RolePath + p, err := utl.BuildAPIPath(RolePath) + if err != nil { + return nil, err + } resp, err := sdk.Client.Get(&p, queryParams) if err != nil { return nil, err diff --git a/pkg/onelogin/saml.go b/pkg/onelogin/saml.go index dd5c1a3..a3a755f 100644 --- a/pkg/onelogin/saml.go +++ b/pkg/onelogin/saml.go @@ -1,8 +1,8 @@ package onelogin import ( - "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/smart_hooks.go b/pkg/onelogin/smart_hooks.go index 244be9d..6c8367d 100644 --- a/pkg/onelogin/smart_hooks.go +++ b/pkg/onelogin/smart_hooks.go @@ -1,8 +1,8 @@ package onelogin import ( - "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/user_mappings.go b/pkg/onelogin/user_mappings.go index dc71d85..c3f4a77 100644 --- a/pkg/onelogin/user_mappings.go +++ b/pkg/onelogin/user_mappings.go @@ -1,8 +1,8 @@ package onelogin import ( - mod "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + mod "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/users.go b/pkg/onelogin/users.go index a3cd93c..bcaeb4d 100644 --- a/pkg/onelogin/users.go +++ b/pkg/onelogin/users.go @@ -5,8 +5,8 @@ import ( "encoding/json" "errors" - mod "github.com/onelogin/onelogin-go-sdk/internal/models" - utl "github.com/onelogin/onelogin-go-sdk/internal/utilities" + mod "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + utl "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/utilities" ) const ( diff --git a/pkg/onelogin/utilities/validPaths.go b/pkg/onelogin/utilities/validPaths.go new file mode 100644 index 0000000..236eced --- /dev/null +++ b/pkg/onelogin/utilities/validPaths.go @@ -0,0 +1,107 @@ +package utilities + +// A list of regex patterns for valid paths +var validPaths = []string{ + "^/api/2/api_authorizations$", + "^/api/2/api_authorizations/[0-9]+$", + "^/api/2/api_authorizations/[0-9]+/scopes$", + "^/api/2/api_authorizations/[0-9]+/scopes/[0-9]+$", + "^/api/2/api_authorizations/[0-9]+/claims$", + "^/api/2/api_authorizations/[0-9]+/claims/[0-9]+$", + "^/api/2/api_authorizations/[0-9]+/clients$", + "^/api/2/api_authorizations/[0-9]+/clients/[0-9]+$", + "^/api/1/events$", + "^/api/1/events/[0-9]+$", + "^/api/1/events/types$", + "^/api/1/groups$", + "^/api/1/groups/[0-9]+$", + "^/api/1/invites/get_invite_link$", + "^/api/1/invites/send_invite_link$", + "^/api/1/users$", + "^/api/1/users/[0-9]+$", + "^/api/1/users/[0-9]+/apps$", + "^/api/1/users/[0-9]+/roles$", + "^/api/1/users/set_password_clear_text/[0-9]+$", + "^/api/1/users/set_password_using_salt/[0-9]+$", + "^/api/1/users/custom_attributes$", + "^/api/1/users/[0-9]+/set_custom_attributes$", + "^/api/1/users/[0-9]+/auth_factor$", + "^/api/1/users/[0-9]+/logout$", + "^/api/1/users/[0-9]+/lock_user$", + "^/api/1/users/[0-9]+/otp_devices$", + "^/api/1/users/[0-9]+/mfa_token$", + "^/api/1/users/[0-9]+/add_roles$", + "^/api/1/users/[0-9]+/set_state$", + "^/api/1/users/[0-9]+/remove_roles$", + "^/api/1/users/[0-9]+/otp_devices/[0-9]+$", + "^/api/1/users/[0-9]+/otp_devices/[0-9]+/verify$", + "^/api/1/users/[0-9]+/otp_devices/[0-9]+/trigger$", + "^/api/1/privileges$", + "^/api/1/privileges/[0-9]+$", + "^/api/1/privileges/[0-9]+/roles$", + "^/api/1/privileges/[0-9]+/roles/[0-9]+$", + "^/api/1/privileges/[0-9]+/users$", + "^/api/1/privileges/[0-9]+/users/[0-9]+$", + "^/api/1/roles$", + "^/api/1/roles/[0-9]+$", + "^/api/1/saml_assertion$", + "^/api/1/saml_assertion/verify_factor$", + "^/api/2/saml_assertion$", + "^/api/2/saml_assertion/verify_factor$", + "^/api/2/mappings$", + "^/api/2/mappings/[0-9]+$", + "^/api/2/mappings/conditions$", + "^/api/2/mappings/conditions/[a-zA-Z0-9]+/operators$", + "^/api/2/mappings/conditions/[a-zA-Z0-9]+/values$", + "^/api/2/mappings/actions$", + "^/api/2/mappings/actions/[a-zA-Z0-9]+/values$", + "^/api/2/mappings/sort$", + "^/api/2/apps$", + "^/api/2/apps/[0-9]+$", + "^/api/2/apps/[0-9]+/parameters/[a-zA-Z0-9]+$", + "^/api/2/apps/[0-9]+/users$", + "^/api/2/apps/[0-9]+/rules$", + "^/api/2/apps/[0-9]+/rules/[a-zA-Z0-9]+$", + "^/api/2/apps/[0-9]+/rules/conditions$", + "^/api/2/apps/[0-9]+/rules/conditions/[a-zA-Z0-9]+/operators$", + "^/api/2/apps/[0-9]+/rules/conditions/[a-zA-Z0-9]+/values$", + "^/api/2/apps/[0-9]+/rules/actions$", + "^/api/2/apps/[0-9]+/rules/actions/[a-zA-Z0-9]+/values$", + "^/api/2/apps/[0-9]+/rules/sort$", + "^/api/2/connectors$", + "^/api/2/risk/rules$", + "^/api/2/risk/rules/[0-9]+$", + "^/api/2/risk/events$", + "^/api/2/risk/scores$", + "^/api/2/risk/verify$", + "^/api/2/mfa/users/[0-9]+/registrations/[0-9]+$", + "^/api/2/mfa/users/[0-9]+/registrations$", + "^/api/2/mfa/users/[0-9]+/devices$", + "^/api/2/mfa/users/[0-9]+/devices/[a-zA-Z0-9]+$", + "^/api/2/mfa/users/[0-9]+/verifications/[a-zA-Z0-9]+$", + "^/api/2/mfa/users/[0-9]+/verifications$", + "^/api/2/mfa/users/[0-9]+/factors$", + "^/api/2/mfa/users/[0-9]+/mfa_token$", + "^/api/2/roles$", + "^/api/2/roles/[0-9]+$", + "^/api/2/roles/[0-9]+/apps$", + "^/api/2/roles/[0-9]+/users$", + "^/api/2/roles/[0-9]+/admins$", + "^/api/2/hooks$", + "^/api/2/hooks/[0-9]+$", + "^/api/2/hooks/[0-9]+/logs$", + "^/api/2/hooks/envs$", + "^/api/2/hooks/envs/[a-zA-Z0-9]+$", + "^/api/2/users$", + "^/api/2/users/[0-9]+$", + "^/api/2/users/[0-9]+/apps$", + "^/api/2/branding/brands$", + "^/api/2/branding/brands/[0-9]+$", + "^/api/2/branding/brands/[0-9]+/templates$", + "^/api/2/branding/brands/[0-9]+/templates/[0-9]+$", + "^/api/2/branding/brands/[0-9]+/apps$", + "^/api/2/branding/email_settings$", + "^/api/2/branding/email_settings/test$", + "^/api/2/branding/brands/[0-9]+/templates/[a-zA-Z]+/[a-zA-Z]+$", + "^/api/2/branding/brands/master/templates/[a-zA-Z]+$", +} diff --git a/pkg/onelogin/utilities/validators.go b/pkg/onelogin/utilities/validators.go new file mode 100644 index 0000000..6a90db8 --- /dev/null +++ b/pkg/onelogin/utilities/validators.go @@ -0,0 +1,53 @@ +package utilities + +import ( + "reflect" + "regexp" + "strings" +) + +// ValidateQueryParams validates the query parameters based on the provided validators. +func ValidateQueryParams(query interface{}, validators map[string]func(interface{}) bool) bool { + queryValue := reflect.ValueOf(query) + if queryValue.Kind() == reflect.Ptr { + queryValue = queryValue.Elem() + } + queryType := queryValue.Type() + + for i := 0; i < queryValue.NumField(); i++ { + fieldValue := queryValue.Field(i) + fieldType := queryType.Field(i) + + // Skip non-pointer fields + if fieldValue.Kind() != reflect.Ptr { + continue + } + + // Skip nil fields + if fieldValue.IsNil() { + continue + } + + fieldName := strings.Split(fieldType.Tag.Get("json"), ",")[0] + + validator, exists := validators[fieldName] + if exists { + if !validator(fieldValue.Interface()) { + return false + } + } + } + + return true +} + +// Check if the constructed path matches any of the allowed path patterns +func IsPathValid(path string) bool { + for _, pattern := range validPaths { + match, _ := regexp.MatchString(pattern, path) + if match { + return true + } + } + return false +} diff --git a/pkg/onelogin/utilities/web.go b/pkg/onelogin/utilities/web.go new file mode 100644 index 0000000..ea0aabb --- /dev/null +++ b/pkg/onelogin/utilities/web.go @@ -0,0 +1,121 @@ +package utilities + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + olerror "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/error" +) + +// receive http response, check error code status, if good return json of resp.Body +// else return error +func CheckHTTPResponse(resp *http.Response) (interface{}, error) { + // Check if the request was successful + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + return nil, fmt.Errorf("request failed with status: %d", resp.StatusCode) + } + + // Read the response body + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + // Close the response body + err = resp.Body.Close() + if err != nil { + return nil, fmt.Errorf("failed to close response body: %w", err) + } + + // Try to unmarshal the response body into a map[string]interface{} or []interface{} + var data interface{} + bodyStr := string(body) + //log.Printf("Response body: %s\n", bodyStr) + if strings.HasPrefix(bodyStr, "[") { + var slice []interface{} + err = json.Unmarshal(body, &slice) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response body into []interface{}: %w", err) + } + data = slice + } else if strings.HasPrefix(bodyStr, "{") { + var dict map[string]interface{} + err = json.Unmarshal(body, &dict) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response body into map[string]interface{}: %w", err) + } + data = dict + } else { + data = bodyStr + } + + //log.Printf("Response body unmarshaled successfully: %v\n", data) + return data, nil +} + +func BuildAPIPath(parts ...interface{}) (string, error) { + var path string + for _, part := range parts { + switch p := part.(type) { + case string: + path += "/" + p + case int: + path += fmt.Sprintf("/%d", p) + default: + // Handle other types if needed + return path, olerror.NewSDKError("Unsupported path type") + } + } + + // Check if the path is valid + if !IsPathValid(path) { + return path, olerror.NewSDKError("Invalid path") + } + + return path, nil +} + +// AddQueryToPath adds the model as a JSON-encoded query parameter to the path and returns the new path. +func AddQueryToPath(path string, query interface{}) (string, error) { + if query == nil { + return path, nil + } + + // Convert query parameters to URL-encoded string + values, err := queryToValues(query) + if err != nil { + return "", err + } + + // Append query parameters to path + if values.Encode() != "" { + path += "?" + values.Encode() + } + + return path, nil +} + +func queryToValues(query interface{}) (url.Values, error) { + values := url.Values{} + + // Convert query parameters to URL-encoded string + if query != nil { + queryBytes, err := json.Marshal(query) + if err != nil { + return nil, err + } + var data map[string]string + if err := json.Unmarshal(queryBytes, &data); err != nil { + return nil, err + } + for key, value := range data { + values.Set(key, value) + } + } + + return values, nil +} diff --git a/readme.md b/readme.md index 19edfe6..31bb5d0 100644 --- a/readme.md +++ b/readme.md @@ -12,8 +12,8 @@ This is the Onelogin SDK, a Go package that provides a convenient interface for - **Data Models**: Represent Onelogin entities and resources. - **Utilities**: Provide utility functions and helper methods. - ## Supported APIs + - [API Authorization](https://developers.onelogin.com/api-docs/2/api-authorization/overview) - [Apps](https://developers.onelogin.com/api-docs/2/apps) - [App Rules](https://developers.onelogin.com/api-docs/2/app-rules) @@ -26,6 +26,7 @@ This is the Onelogin SDK, a Go package that provides a convenient interface for - [User Mappings](https://developers.onelogin.com/api-docs/2/user-mappings) ## Partially Support APIs + - [MFA](https://developers.onelogin.com/api-docs/2/multi-factor-authentication) ## Installation @@ -43,6 +44,7 @@ The SDK expects three environment variables to be set for authentication: - `ONELOGIN_CLIENT_ID` - `ONELOGIN_CLIENT_SECRET` - `ONELOGIN_SUBDOMAIN` +- `ONELOGIN_TIMEOUT` These variables are used by the Authenticator for authentication with the OneLogin API. The Authenticator retrieves an access token using these credentials, which is then used for API requests. You can set these variables directly in your shell or in the environment of the program that will be using this SDK. @@ -52,6 +54,7 @@ In a Unix/Linux shell, you can use the `export` command to set these variables: export ONELOGIN_CLIENT_ID=your_client_id export ONELOGIN_CLIENT_SECRET=your_client_secret export ONELOGIN_SUBDOMAIN=your_subdomain +export ONELOGIN_TIMEOUT=15 ``` In a Go program, you can use the `os` package to set these variables: @@ -60,6 +63,7 @@ In a Go program, you can use the `os` package to set these variables: os.Setenv("ONELOGIN_CLIENT_ID", "your_client_id") os.Setenv("ONELOGIN_CLIENT_SECRET", "your_client_secret") os.Setenv("ONELOGIN_SUBDOMAIN", "your_subdomain") +os.Setenv("ONELOGIN_TIMEOUT", 15) ``` Please ensure these variables are set before attempting to use the SDK to make API requests. @@ -72,33 +76,33 @@ Here's an example demonstrating how to use the Onelogin SDK: package main import ( - "fmt" + "fmt" - "github.com/onelogin/onelogin-go-sdk/internal/models" - "github.com/onelogin/onelogin-go-sdk/pkg/onelogin" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/models" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin" ) func main() { - ol, err := onelogin.NewOneloginSDK() - if err != nil { - fmt.Println("Unable to initialize client:", err) - return - } - userQuery := models.UserQuery{} - userList, err := ol.GetUsers(&userQuery) - if err != nil { - fmt.Println("Failed to get user:", err) - return - } - fmt.Println(userList) - - appQuery := models.AppQuery{} - appList, err := ol.GetApps(&appQuery) - if err != nil { - fmt.Println("Failed to get app list:", err) - return - } - fmt.Println("App List:", appList) + ol, err := onelogin.NewOneloginSDK() + if err != nil { + fmt.Println("Unable to initialize client:", err) + return + } + userQuery := models.UserQuery{} + userList, err := ol.GetUsers(&userQuery) + if err != nil { + fmt.Println("Failed to get user:", err) + return + } + fmt.Println(userList) + + appQuery := models.AppQuery{} + appList, err := ol.GetApps(&appQuery) + if err != nil { + fmt.Println("Failed to get app list:", err) + return + } + fmt.Println("App List:", appList) } ``` @@ -115,4 +119,4 @@ func main() { ## Contributing -Contributions to the Onelogin SDK are welcome! If you encounter any issues or have suggestions for improvement, please open an issue or submit a pull request. We appreciate your feedback and contributions. \ No newline at end of file +Contributions to the Onelogin SDK are welcome! If you encounter any issues or have suggestions for improvement, please open an issue or submit a pull request. We appreciate your feedback and contributions. diff --git a/tests/api_test.go b/tests/api_test.go index 3b6db01..7fb5b96 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -6,8 +6,8 @@ import ( "net/http" "testing" - "github.com/onelogin/onelogin-go-sdk/internal/api" - "github.com/onelogin/onelogin-go-sdk/internal/authentication" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/api" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/authentication" ) type MockHttpClient struct { @@ -39,7 +39,7 @@ func createMockClient() *api.Client { return "mockToken", nil } - auth := authentication.NewAuthenticator() + auth := authentication.NewAuthenticator("test") client := &api.Client{ HttpClient: mockClient, Auth: auth, diff --git a/tests/error_handling_test.go b/tests/error_handling_test.go index adeb9bd..b4ef5b0 100644 --- a/tests/error_handling_test.go +++ b/tests/error_handling_test.go @@ -3,8 +3,7 @@ package tests import ( "errors" "testing" - - "github.com/onelogin/onelogin-go-sdk/internal/error" + "github.com/onelogin/onelogin-go-sdk/v4/pkg/onelogin/error" ) func TestNewSerializationError(t *testing.T) {