diff --git a/docs/auth0_apps_session-transfer_update.md b/docs/auth0_apps_session-transfer_update.md index 5081e575d..e86a59f79 100644 --- a/docs/auth0_apps_session-transfer_update.md +++ b/docs/auth0_apps_session-transfer_update.md @@ -18,6 +18,7 @@ auth0 apps session-transfer update [flags] auth0 apps session-transfer update auth0 apps session-transfer update auth0 apps session-transfer update --can-create-token --json + auth0 apps session-transfer update --delegation-allow-delegated-access=true --delegation-enforce-device-binding=asn auth0 apps session-transfer update --can-create-token=true --allowed-auth-methods=cookie,query --enforce-device-binding=ip ``` @@ -25,11 +26,13 @@ auth0 apps session-transfer update [flags] ## Flags ``` - -m, --allowed-auth-methods strings Comma-separated list of authentication methods (e.g., cookie, query). - -t, --can-create-token Allow creation of session transfer tokens. - -e, --enforce-device-binding string Device binding enforcement: 'none', 'ip', or 'asn'. - --json Output in json format. - --json-compact Output in compact json format. + -m, --allowed-auth-methods strings Comma-separated list of authentication methods (e.g., cookie, query). + -t, --can-create-token Allow creation of session transfer tokens. + -d, --delegation-allow-delegated-access (Early Access) Allow the application to accept Session Transfer Tokens containing an Actor, enabling delegated (impersonation) access. Defaults to false. + -b, --delegation-enforce-device-binding string (Early Access) Device binding enforcement for delegated (impersonation) access: 'ip' or 'asn'. Defaults to 'ip'. + -e, --enforce-device-binding string Device binding enforcement: 'none', 'ip', or 'asn'. + --json Output in json format. + --json-compact Output in compact json format. ``` diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 82481ac8d..4e9f55b4e 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -164,6 +164,20 @@ var ( Help: "Device binding enforcement: 'none', 'ip', or 'asn'.", AlwaysPrompt: true, } + appSTDelegationAllowAccess = Flag{ + Name: "Allow Delegated Access", + LongForm: "delegation-allow-delegated-access", + ShortForm: "d", + Help: "(Early Access) Allow the application to accept Session Transfer Tokens containing an Actor, " + + "enabling delegated (impersonation) access. Defaults to false.", + } + appSTDelegationDeviceBinding = Flag{ + Name: "Delegation Enforce Device Binding", + LongForm: "delegation-enforce-device-binding", + ShortForm: "b", + Help: "(Early Access) Device binding enforcement for delegated (impersonation) access: 'ip' or 'asn'. " + + "Defaults to 'ip'.", + } refreshToken = Flag{ Name: "Refresh Token", LongForm: "refresh-token", @@ -1211,10 +1225,12 @@ func appsSessionTransferShowCmd(cli *cli) *cobra.Command { func appsSessionTransferUpdateCmd(cli *cli) *cobra.Command { var inputs struct { - ID string - CanCreateToken bool - AllowedAuthMethods []string - EnforceDeviceBinding string + ID string + CanCreateToken bool + AllowedAuthMethods []string + EnforceDeviceBinding string + DelegationAllowAccess bool + DelegationDeviceBinding string } cmd := &cobra.Command{ @@ -1224,6 +1240,7 @@ func appsSessionTransferUpdateCmd(cli *cli) *cobra.Command { Example: ` auth0 apps session-transfer update auth0 apps session-transfer update auth0 apps session-transfer update --can-create-token --json + auth0 apps session-transfer update --delegation-allow-delegated-access=true --delegation-enforce-device-binding=asn auth0 apps session-transfer update --can-create-token=true --allowed-auth-methods=cookie,query --enforce-device-binding=ip`, RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { @@ -1283,6 +1300,22 @@ func appsSessionTransferUpdateCmd(cli *cli) *cobra.Command { st.EnforceDeviceBinding = current.SessionTransfer.EnforceDeviceBinding } + // Delegation (EA) is sent only when a flag is set, leaving it untouched for + // others. The API merges sub-fields, so sending just the changed one is enough. + if appSTDelegationAllowAccess.IsSet(cmd) || appSTDelegationDeviceBinding.IsSet(cmd) { + delegation := &management.SessionTransferDelegation{} + + if appSTDelegationAllowAccess.IsSet(cmd) { + delegation.AllowDelegatedAccess = &inputs.DelegationAllowAccess + } + + if appSTDelegationDeviceBinding.IsSet(cmd) { + delegation.EnforceDeviceBinding = &inputs.DelegationDeviceBinding + } + + st.Delegation = delegation + } + // Send update request. clientST := &management.Client{SessionTransfer: &st} if err := ansi.Waiting(func() error { @@ -1302,6 +1335,8 @@ func appsSessionTransferUpdateCmd(cli *cli) *cobra.Command { appSTCanCreateToken.RegisterBoolU(cmd, &inputs.CanCreateToken, false) appSTAllowedAuthMethods.RegisterStringSliceU(cmd, &inputs.AllowedAuthMethods, nil) appSTEnforceDeviceBinding.RegisterStringU(cmd, &inputs.EnforceDeviceBinding, "") + appSTDelegationAllowAccess.RegisterBoolU(cmd, &inputs.DelegationAllowAccess, false) + appSTDelegationDeviceBinding.RegisterStringU(cmd, &inputs.DelegationDeviceBinding, "") return cmd } diff --git a/internal/display/apps_session_transfer.go b/internal/display/apps_session_transfer.go index b5a9bd08b..7534034dd 100644 --- a/internal/display/apps_session_transfer.go +++ b/internal/display/apps_session_transfer.go @@ -12,6 +12,11 @@ type SessionTransferView struct { AllowedMethods string DeviceBinding string + // Delegation (EA) fields, shown only when hasDelegation is true. + hasDelegation bool + DelegationAllowAccess string + DelegationDeviceBinding string + raw interface{} } @@ -29,12 +34,21 @@ func (v *SessionTransferView) AsTableRow() []string { } func (v *SessionTransferView) KeyValues() [][]string { - return [][]string{ + keyValues := [][]string{ {"CLIENT ID", v.ID}, {"CAN CREATE TOKEN", v.CanCreateTOKEN}, {"ALLOWED METHODS", v.AllowedMethods}, {"DEVICE BINDING", v.DeviceBinding}, } + + if v.hasDelegation { + keyValues = append(keyValues, + []string{"ALLOW DELEGATED ACCESS", v.DelegationAllowAccess}, + []string{"DELEGATION DEVICE BINDING", v.DelegationDeviceBinding}, + ) + } + + return keyValues } func (v *SessionTransferView) Object() interface{} { @@ -54,11 +68,19 @@ func (r *Renderer) SessionTransferUpdate(client *management.Client, id string) { } func MakeSessionTransferView(client *management.Client) *SessionTransferView { - return &SessionTransferView{ + view := &SessionTransferView{ ID: client.GetClientID(), CanCreateTOKEN: boolean(client.SessionTransfer.GetCanCreateSessionTransferToken()), AllowedMethods: stringSliceToCommaSeparatedString(client.SessionTransfer.GetAllowedAuthenticationMethods()), DeviceBinding: client.SessionTransfer.GetEnforceDeviceBinding(), raw: client.SessionTransfer, } + + if delegation := client.GetSessionTransfer().GetDelegation(); delegation != nil { + view.hasDelegation = true + view.DelegationAllowAccess = boolean(delegation.GetAllowDelegatedAccess()) + view.DelegationDeviceBinding = delegation.GetEnforceDeviceBinding() + } + + return view } diff --git a/internal/display/apps_session_transfer_test.go b/internal/display/apps_session_transfer_test.go new file mode 100644 index 000000000..5cfecfabc --- /dev/null +++ b/internal/display/apps_session_transfer_test.go @@ -0,0 +1,67 @@ +package display + +import ( + "testing" + + "github.com/auth0/go-auth0" + "github.com/auth0/go-auth0/management" + "github.com/stretchr/testify/assert" +) + +func TestMakeSessionTransferView_WithoutDelegation(t *testing.T) { + client := &management.Client{ + ClientID: auth0.String("client-id"), + SessionTransfer: &management.SessionTransfer{ + CanCreateSessionTransferToken: auth0.Bool(true), + AllowedAuthenticationMethods: &[]string{"cookie", "query"}, + EnforceDeviceBinding: auth0.String("ip"), + }, + } + + view := MakeSessionTransferView(client) + + assert.False(t, view.hasDelegation) + assert.Equal(t, "", view.DelegationAllowAccess) + assert.Equal(t, "", view.DelegationDeviceBinding) + + // Delegation rows must be omitted when no delegation is configured. + keyValues := view.KeyValues() + assert.Equal(t, [][]string{ + {"CLIENT ID", "client-id"}, + {"CAN CREATE TOKEN", boolean(true)}, + {"ALLOWED METHODS", "cookie, query"}, + {"DEVICE BINDING", "ip"}, + }, keyValues) +} + +func TestMakeSessionTransferView_WithDelegation(t *testing.T) { + client := &management.Client{ + ClientID: auth0.String("client-id"), + SessionTransfer: &management.SessionTransfer{ + CanCreateSessionTransferToken: auth0.Bool(true), + AllowedAuthenticationMethods: &[]string{"cookie"}, + EnforceDeviceBinding: auth0.String("ip"), + Delegation: &management.SessionTransferDelegation{ + AllowDelegatedAccess: auth0.Bool(true), + EnforceDeviceBinding: auth0.String("asn"), + }, + }, + } + + view := MakeSessionTransferView(client) + + assert.True(t, view.hasDelegation) + assert.Equal(t, boolean(true), view.DelegationAllowAccess) + assert.Equal(t, "asn", view.DelegationDeviceBinding) + + // Delegation rows must be appended after the base session-transfer rows. + keyValues := view.KeyValues() + assert.Equal(t, [][]string{ + {"CLIENT ID", "client-id"}, + {"CAN CREATE TOKEN", boolean(true)}, + {"ALLOWED METHODS", "cookie"}, + {"DEVICE BINDING", "ip"}, + {"ALLOW DELEGATED ACCESS", boolean(true)}, + {"DELEGATION DEVICE BINDING", "asn"}, + }, keyValues) +}