From 1c931a57421118e39fac503dec28b6f9a8636b2c Mon Sep 17 00:00:00 2001 From: Joakim Tangnes Date: Fri, 15 Mar 2024 10:09:59 +0100 Subject: [PATCH] feat(management): add DeviceCredentialsManager to mange device credentials --- management/device_credentials.go | 60 ++++++++ management/device_credentials_test.go | 81 ++++++++++ management/management.gen.go | 61 ++++++++ management/management.gen_test.go | 78 ++++++++++ management/management.go | 4 + .../TestDeviceCredentials_Create.yaml | 74 +++++++++ .../TestDeviceCredentials_Delete.yaml | 145 ++++++++++++++++++ .../TestDeviceCredentials_List.yaml | 110 +++++++++++++ 8 files changed, 613 insertions(+) create mode 100644 management/device_credentials.go create mode 100644 management/device_credentials_test.go create mode 100644 test/data/recordings/TestDeviceCredentials_Create.yaml create mode 100644 test/data/recordings/TestDeviceCredentials_Delete.yaml create mode 100644 test/data/recordings/TestDeviceCredentials_List.yaml diff --git a/management/device_credentials.go b/management/device_credentials.go new file mode 100644 index 00000000..0c722049 --- /dev/null +++ b/management/device_credentials.go @@ -0,0 +1,60 @@ +package management + +import "context" + +type DeviceCredential struct { + // ID of this device credential + ID *string `json:"id,omitempty"` + + // The id of the client. + ClientID *string `json:"client_id,omitempty"` + + // The id of the user. + UserID *string `json:"user_id,omitempty"` + + // User agent for this device + DeviceName *string `json:"device_name,omitempty"` + + // Unique identifier for the device. NOTE: This field is generally not populated for refresh_tokens and rotating_refresh_tokens + DeviceID *string `json:"device_id,omitempty"` + + // Type of credential. Can be public_key, refresh_token, or rotating_refresh_token + Type *string `json:"type,omitempty"` + + // Base64 encoded string containing the credential + Value *string `json:"value,omitempty"` +} + +// DeviceCredentialList is a list of DeviceCredentials +type DeviceCredentialList struct { + List + DeviceCredentials []*DeviceCredential `json:"device_credentials"` +} + +// DeviceCredentialsManager manages Auth0 device-credentials resources. +type DeviceCredentialsManager manager + +// Create a device credential public key to manage refresh token rotation for a given user_id +// Type of credential must be "public_key". +// +// See: https://auth0.com/docs/api/management/v2/device-credentials/post-device-credentials +func (m *DeviceCredentialsManager) Create(ctx context.Context, d *DeviceCredential, opts ...RequestOption) error { + return m.management.Request(ctx, "POST", m.management.URI("device-credentials"), d, opts...) +} + +// Retrieve device credential information (public_key, refresh_token, or rotating_refresh_token) associated with a specific user. +// +// For information on how to paginate using this function see https://pkg.go.dev/github.com/auth0/go-auth0/management#hdr-Page_Based_Pagination +// +// See: https://auth0.com/docs/api/management/v2/device-credentials/get-device-credentials +func (m *DeviceCredentialsManager) List(ctx context.Context, opts ...RequestOption) (d *DeviceCredentialList, err error) { + err = m.management.Request(ctx, "GET", m.management.URI("device-credentials"), &d, applyListDefaults(opts)) + return +} + +// Permanently delete a device credential (such as a refresh token or public key) with the given ID. +// +// See: https://auth0.com/docs/api/management/v2/device-credentials/delete-device-credentials-by-id +func (m *DeviceCredentialsManager) Delete(ctx context.Context, id string, opts ...RequestOption) error { + return m.management.Request(ctx, "DELETE", m.management.URI("device-credentials", id), nil, opts...) +} diff --git a/management/device_credentials_test.go b/management/device_credentials_test.go new file mode 100644 index 00000000..1289e305 --- /dev/null +++ b/management/device_credentials_test.go @@ -0,0 +1,81 @@ +package management + +import ( + "context" + "testing" + + "github.com/auth0/go-auth0" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeviceCredentials_Create(t *testing.T) { + configureHTTPTestRecordings(t) + + expectedDeviceCredential := &DeviceCredential{ + DeviceName: auth0.String("TestDevice"), + Type: auth0.String("public_key"), + Value: auth0.String("ABCD"), + DeviceID: auth0.String("test_device"), + ClientID: auth0.String("test_client"), + } + + err := api.DeviceCredentials.Create(context.Background(), expectedDeviceCredential) + assert.NoError(t, err) + assert.NotEmpty(t, expectedDeviceCredential.GetID()) + + t.Cleanup(func() { + cleanupDeviceCredential(t, expectedDeviceCredential.GetID()) + }) +} + +func TestDeviceCredentials_List(t *testing.T) { + configureHTTPTestRecordings(t) + + expectedDeviceCredential := givenADeviceCredential(t) + + deviceCredentialList, err := api.DeviceCredentials.List(context.Background(), IncludeFields("id")) + + assert.NoError(t, err) + assert.Contains(t, deviceCredentialList.DeviceCredentials, &DeviceCredential{ID: expectedDeviceCredential.ID}) +} + +func TestDeviceCredentials_Delete(t *testing.T) { + configureHTTPTestRecordings(t) + + expectedDeviceCredential := givenADeviceCredential(t) + + err := api.DeviceCredentials.Delete(context.Background(), expectedDeviceCredential.GetID()) + assert.NoError(t, err) + + actualDeviceCredentials, err := api.DeviceCredentials.List(context.Background()) + assert.NoError(t, err) + assert.Empty(t, actualDeviceCredentials.DeviceCredentials) +} + +func givenADeviceCredential(t *testing.T) *DeviceCredential { + t.Helper() + + deviceCredential := &DeviceCredential{ + DeviceName: auth0.String("TestDevice"), + Type: auth0.String("refresh_token"), + Value: auth0.String("ABCD"), + DeviceID: auth0.String("test_device"), + ClientID: auth0.String("test_client"), + } + err := api.DeviceCredentials.Create(context.Background(), deviceCredential) + require.NoError(t, err) + + t.Cleanup(func() { + cleanupDeviceCredential(t, deviceCredential.GetID()) + }) + + return deviceCredential +} + +func cleanupDeviceCredential(t *testing.T, id string) { + t.Helper() + + err := api.DeviceCredentials.Delete(context.Background(), id) + require.NoError(t, err) +} diff --git a/management/management.gen.go b/management/management.gen.go index 89a21a03..d31efb7b 100644 --- a/management/management.gen.go +++ b/management/management.gen.go @@ -5927,6 +5927,67 @@ func (d *DailyStat) String() string { return Stringify(d) } +// GetClientID returns the ClientID field if it's non-nil, zero value otherwise. +func (d *DeviceCredential) GetClientID() string { + if d == nil || d.ClientID == nil { + return "" + } + return *d.ClientID +} + +// GetDeviceID returns the DeviceID field if it's non-nil, zero value otherwise. +func (d *DeviceCredential) GetDeviceID() string { + if d == nil || d.DeviceID == nil { + return "" + } + return *d.DeviceID +} + +// GetDeviceName returns the DeviceName field if it's non-nil, zero value otherwise. +func (d *DeviceCredential) GetDeviceName() string { + if d == nil || d.DeviceName == nil { + return "" + } + return *d.DeviceName +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (d *DeviceCredential) GetID() string { + if d == nil || d.ID == nil { + return "" + } + return *d.ID +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (d *DeviceCredential) GetType() string { + if d == nil || d.Type == nil { + return "" + } + return *d.Type +} + +// GetUserID returns the UserID field if it's non-nil, zero value otherwise. +func (d *DeviceCredential) GetUserID() string { + if d == nil || d.UserID == nil { + return "" + } + return *d.UserID +} + +// GetValue returns the Value field if it's non-nil, zero value otherwise. +func (d *DeviceCredential) GetValue() string { + if d == nil || d.Value == nil { + return "" + } + return *d.Value +} + +// String returns a string representation of DeviceCredential. +func (d *DeviceCredential) String() string { + return Stringify(d) +} + // String returns a string representation of DropboxClientAddon. func (d *DropboxClientAddon) String() string { return Stringify(d) diff --git a/management/management.gen_test.go b/management/management.gen_test.go index af462fbb..64e3e1d0 100644 --- a/management/management.gen_test.go +++ b/management/management.gen_test.go @@ -7356,6 +7356,84 @@ func TestDailyStat_String(t *testing.T) { } } +func TestDeviceCredential_GetClientID(tt *testing.T) { + var zeroValue string + d := &DeviceCredential{ClientID: &zeroValue} + d.GetClientID() + d = &DeviceCredential{} + d.GetClientID() + d = nil + d.GetClientID() +} + +func TestDeviceCredential_GetDeviceID(tt *testing.T) { + var zeroValue string + d := &DeviceCredential{DeviceID: &zeroValue} + d.GetDeviceID() + d = &DeviceCredential{} + d.GetDeviceID() + d = nil + d.GetDeviceID() +} + +func TestDeviceCredential_GetDeviceName(tt *testing.T) { + var zeroValue string + d := &DeviceCredential{DeviceName: &zeroValue} + d.GetDeviceName() + d = &DeviceCredential{} + d.GetDeviceName() + d = nil + d.GetDeviceName() +} + +func TestDeviceCredential_GetID(tt *testing.T) { + var zeroValue string + d := &DeviceCredential{ID: &zeroValue} + d.GetID() + d = &DeviceCredential{} + d.GetID() + d = nil + d.GetID() +} + +func TestDeviceCredential_GetType(tt *testing.T) { + var zeroValue string + d := &DeviceCredential{Type: &zeroValue} + d.GetType() + d = &DeviceCredential{} + d.GetType() + d = nil + d.GetType() +} + +func TestDeviceCredential_GetUserID(tt *testing.T) { + var zeroValue string + d := &DeviceCredential{UserID: &zeroValue} + d.GetUserID() + d = &DeviceCredential{} + d.GetUserID() + d = nil + d.GetUserID() +} + +func TestDeviceCredential_GetValue(tt *testing.T) { + var zeroValue string + d := &DeviceCredential{Value: &zeroValue} + d.GetValue() + d = &DeviceCredential{} + d.GetValue() + d = nil + d.GetValue() +} + +func TestDeviceCredential_String(t *testing.T) { + var rawJSON json.RawMessage + v := &DeviceCredential{} + if err := json.Unmarshal([]byte(v.String()), &rawJSON); err != nil { + t.Errorf("failed to produce a valid json") + } +} + func TestDropboxClientAddon_String(t *testing.T) { var rawJSON json.RawMessage v := &DropboxClientAddon{} diff --git a/management/management.go b/management/management.go index f4cf979e..0d488201 100644 --- a/management/management.go +++ b/management/management.go @@ -31,6 +31,9 @@ type Management struct { // CustomDomain manages Auth0 Custom Domains. CustomDomain *CustomDomainManager + // DeviceCredentials manages Auth0 device credentials. + DeviceCredentials *DeviceCredentialsManager + // Grant manages Auth0 Grants. Grant *GrantManager @@ -168,6 +171,7 @@ func New(domain string, options ...Option) (*Management, error) { m.ClientGrant = (*ClientGrantManager)(&m.common) m.Connection = (*ConnectionManager)(&m.common) m.CustomDomain = (*CustomDomainManager)(&m.common) + m.DeviceCredentials = (*DeviceCredentialsManager)(&m.common) m.EmailProvider = (*EmailProviderManager)(&m.common) m.EmailTemplate = (*EmailTemplateManager)(&m.common) m.Grant = (*GrantManager)(&m.common) diff --git a/test/data/recordings/TestDeviceCredentials_Create.yaml b/test/data/recordings/TestDeviceCredentials_Create.yaml new file mode 100644 index 00000000..3eb0e336 --- /dev/null +++ b/test/data/recordings/TestDeviceCredentials_Create.yaml @@ -0,0 +1,74 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 178 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + {"client_id":"test_client","device_name":"TestDevice","device_id":"test_device","type":"public_key","value":"ABCD"} + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 26 + uncompressed: false + body: '{"id":"test_credential"}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 201 Created + code: 201 + duration: 100ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials/test_credential + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Content-Type: + - application/json; charset=utf-8 + status: 204 No Content + code: 204 + duration: 100ms diff --git a/test/data/recordings/TestDeviceCredentials_Delete.yaml b/test/data/recordings/TestDeviceCredentials_Delete.yaml new file mode 100644 index 00000000..cdc5f430 --- /dev/null +++ b/test/data/recordings/TestDeviceCredentials_Delete.yaml @@ -0,0 +1,145 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 178 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + {"client_id":"test_client","device_name":"TestDevice","device_id":"test_device","type":"public_key","value":"ABCD"} + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 26 + uncompressed: false + body: '{"id":"test_credential"}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 201 Created + code: 201 + duration: 100ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials/test_credential + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Content-Type: + - application/json; charset=utf-8 + status: 204 No Content + code: 204 + duration: 100ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials?include_totals=true&per_page=50 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 56 + uncompressed: true + body: '{"total":0,"start":0,"limit":50,"device_credentials":[]}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 Ok + code: 200 + duration: 100ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials/test_credential + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Content-Type: + - application/json; charset=utf-8 + status: 204 No Content + code: 204 + duration: 100ms diff --git a/test/data/recordings/TestDeviceCredentials_List.yaml b/test/data/recordings/TestDeviceCredentials_List.yaml new file mode 100644 index 00000000..5956966c --- /dev/null +++ b/test/data/recordings/TestDeviceCredentials_List.yaml @@ -0,0 +1,110 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 178 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + {"client_id":"test_client","device_name":"TestDevice","device_id":"test_device","type":"public_key","value":"ABCD"} + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 26 + uncompressed: false + body: '{"id":"test_credential"}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 201 Created + code: 201 + duration: 100ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 5 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: | + null + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials?fields=id&include_fields=true&include_totals=true&per_page=50 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: -1 + uncompressed: true + body: '{"total":1,"start":0,"limit":50,"device_credentials":[{"id":"test_credential"}]}' + headers: + Content-Type: + - application/json; charset=utf-8 + status: 200 OK + code: 200 + duration: 100ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: go-auth0-dev.eu.auth0.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - Go-Auth0-SDK/latest + url: https://go-auth0-dev.eu.auth0.com/api/v2/device-credentials/test_credential + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Content-Type: + - application/json; charset=utf-8 + status: 204 No Content + code: 204 + duration: 100ms