Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion management/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Client struct {
// Additional possible values: "rms", "box", "cloudbees", "concur", "dropbox",
// "mscrm", "echosign", "egnyte", "newrelic", "office365", "salesforce",
// "sentry", "sharepoint", "slack", "springcm", "zendesk", "zoom",
// "sso_integration", "oag".
// "sso_integration", "oag", "express_configuration".
AppType *string `json:"app_type,omitempty"`

// The URL of the client logo (recommended size: 150x150).
Expand Down Expand Up @@ -212,6 +212,40 @@ type Client struct {
// For more details on making custom requests, refer to the Auth0 Go SDK examples:
// https://github.com/auth0/go-auth0/blob/main/EXAMPLES.md#providing-a-custom-user-struct
AsyncApprovalNotificationChannels *[]string `json:"async_approval_notification_channels,omitempty"`
// ExpressConfiguration holds the express configuration for the client.
// Application-specific configuration for use with the OIN Express Configuration feature
ExpressConfiguration *ExpressConfiguration `json:"express_configuration,omitempty"`
}

// ExpressConfiguration represents the OIN Express Configuration settings for a client.
// This is used for Okta Integration Network (OIN) applications to configure SSO and SCIM provisioning.
type ExpressConfiguration struct {
// InitiateLoginURITemplate is the URI users should bookmark to log in to this application.
// Variable substitution is permitted for the following properties: organization_name, organization_id, and connection_name.
InitiateLoginURITemplate *string `json:"initiate_login_uri_template,omitempty"`
// UserAttributeProfileID is the ID of the user attribute profile to use for this application.
UserAttributeProfileID *string `json:"user_attribute_profile_id,omitempty"`
// ConnectionProfileID is the ID of the connection profile to use for this application.
ConnectionProfileID *string `json:"connection_profile_id,omitempty"`
// EnableClient indicates when true, all connections made via express configuration will be enabled for this application.
EnableClient *bool `json:"enable_client,omitempty"`
// EnableOrganization When true, all connections made via express configuration will have the associated organization enabled.
EnableOrganization *bool `json:"enable_organization,omitempty"`
// LinkedClients is a list of client IDs that are linked to this express configuration (e.g. web or mobile clients).
LinkedClients *[]LinkedClient `json:"linked_clients,omitempty"`
// OktaOINClientID is the unique identifier for the Okta OIN Express Configuration Client, which Okta will use for this application.
OktaOINClientID *string `json:"okta_oin_client_id,omitempty"`
// AdminLoginDomain is the domain that admins are expected to log in via for authenticating for express configuration.
AdminLoginDomain *string `json:"admin_login_domain,omitempty"`
// OINSubmissionID is the identifier of the published application in the OKTA OIN.
OINSubmissionID *string `json:"oin_submission_id,omitempty"`
}

// LinkedClient represents a client that is linked to an Express Configuration client.
// This is typically used to associate web or mobile clients with an OIN Express Configuration.
type LinkedClient struct {
// ClientID is the ID of the linked client.
ClientID *string `json:"client_id,omitempty"`
}

// ClientTokenExchange allows configuration for token exchange.
Expand Down
3 changes: 3 additions & 0 deletions management/client_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type ClientGrant struct {

// AuthorizationDetailsTypes defines the types of authorization details allowed for this client grant.
AuthorizationDetailsTypes *[]string `json:"authorization_details_types,omitempty"`

// IsSystem indicates whether this grant is a special grant created by Auth0. It cannot be modified or deleted directly.
IsSystem *bool `json:"is_system,omitempty"`
}

// ClientGrantList is a list of ClientGrants.
Expand Down
152 changes: 152 additions & 0 deletions management/client_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package management
import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -155,3 +156,154 @@ func cleanupClientGrant(t *testing.T, clientGrantID string) {
err := api.ClientGrant.Delete(context.Background(), clientGrantID)
require.NoError(t, err)
}

func TestClientGrant_ExpressConfiguration(t *testing.T) {
configureHTTPTestRecordings(t)

t.Run("Express Configuration Client has System Grants", func(t *testing.T) {
// Create an Express Configuration client
expressClient := &Client{
Name: auth0.Stringf("Express Config Client (%s)", time.Now().Format(time.StampMilli)),
AppType: auth0.String("express_configuration"),
}

err := api.Client.Create(context.Background(), expressClient)
require.NoError(t, err)

t.Cleanup(func() {
cleanupClient(t, expressClient.GetClientID())
})

// List all client grants filtered by this client
grantList, err := api.ClientGrant.List(
context.Background(),
Parameter("client_id", expressClient.GetClientID()),
)
require.NoError(t, err)

// Express Configuration clients should have system-created grants
t.Logf("Found %d client grants for Express Configuration client %s", len(grantList.ClientGrants), expressClient.GetClientID())

for _, grant := range grantList.ClientGrants {
t.Logf("Grant ID: %s, Audience: %s, IsSystem: %v, SubjectType: %s",
grant.GetID(),
grant.GetAudience(),
grant.GetIsSystem(),
grant.GetSubjectType(),
)

// System grants for Express Configuration should have is_system=true
if grant.GetIsSystem() {
assert.True(t, grant.GetIsSystem(), "Express Configuration grants should be system grants")
assert.Equal(t, expressClient.GetClientID(), grant.GetClientID())
}
}
})

t.Run("Cannot Manually Create Grant for Express Configuration Client", func(t *testing.T) {
// Create an Express Configuration client
expressClient := &Client{
Name: auth0.Stringf("Express Config Client (%s)", time.Now().Format(time.StampMilli)),
AppType: auth0.String("express_configuration"),
}

err := api.Client.Create(context.Background(), expressClient)
require.NoError(t, err)

t.Cleanup(func() {
cleanupClient(t, expressClient.GetClientID())
})

// Try to create a client grant for this Express Configuration client
resourceServer := givenAResourceServer(t)

clientGrant := &ClientGrant{
ClientID: expressClient.ClientID,
Audience: resourceServer.Identifier,
Scope: &[]string{"create:resource"},
SubjectType: auth0.String("client"),
}

err = api.ClientGrant.Create(context.Background(), clientGrant)

// Should fail because Express Configuration clients cannot have manual grants
assert.Error(t, err)
assert.Contains(t, err.Error(), "express_configuration")
})
}

func TestClientGrant_IsSystem(t *testing.T) {
configureHTTPTestRecordings(t)

t.Run("User Created Grant has is_system false", func(t *testing.T) {
// Create a client and resource server
client := givenAClient(t)
resourceServer := givenAResourceServer(t)

// Create a simple client grant without authorization_details_types
clientGrant := &ClientGrant{
ClientID: client.ClientID,
Audience: resourceServer.Identifier,
Scope: &[]string{"create:resource"},
SubjectType: auth0.String("user"),
}

err := api.ClientGrant.Create(context.Background(), clientGrant)
require.NoError(t, err)

t.Cleanup(func() {
cleanupClientGrant(t, clientGrant.GetID())
})

// Read it back
retrievedGrant, err := api.ClientGrant.Read(context.Background(), clientGrant.GetID())
require.NoError(t, err)

// Verify is_system is false for user-created grants
assert.False(t, retrievedGrant.GetIsSystem())
})

t.Run("List Grants and Check is_system Field", func(t *testing.T) {
// Create a client and resource server
client := givenAClient(t)
resourceServer := givenAResourceServer(t)

// Create a user client grant
userGrant := &ClientGrant{
ClientID: client.ClientID,
Audience: resourceServer.Identifier,
Scope: &[]string{"create:resource"},
SubjectType: auth0.String("user"),
}

err := api.ClientGrant.Create(context.Background(), userGrant)
require.NoError(t, err)

t.Cleanup(func() {
cleanupClientGrant(t, userGrant.GetID())
})

// List all grants
grantList, err := api.ClientGrant.List(context.Background())
require.NoError(t, err)

// Find our user-created grant
foundUserGrant := false

for _, grant := range grantList.ClientGrants {
if grant.GetID() == userGrant.GetID() {
foundUserGrant = true

assert.False(t, grant.GetIsSystem(), "User-created grant should have is_system=false")
}
// System grants should have is_system=true
if grant.GetIsSystem() {
assert.True(t, grant.GetIsSystem(), "System grant should have is_system=true")
// System grants cannot be deleted/modified
assert.NotEmpty(t, grant.GetID())
}
}

assert.True(t, foundUserGrant, "Should find the user-created grant in the list")
})
}
Loading
Loading