diff --git a/sdk/storage/azfile/CHANGELOG.md b/sdk/storage/azfile/CHANGELOG.md index cab1236b22d6..b80694ee95c6 100644 --- a/sdk/storage/azfile/CHANGELOG.md +++ b/sdk/storage/azfile/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features Added * Updated service version to STG 87(2022-11-02). +* Added OAuth support. ### Breaking Changes diff --git a/sdk/storage/azfile/assets.json b/sdk/storage/azfile/assets.json index 297645fadc04..b132a321ed62 100644 --- a/sdk/storage/azfile/assets.json +++ b/sdk/storage/azfile/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "go", "TagPrefix": "go/storage/azfile", - "Tag": "go/storage/azfile_600a15563c" + "Tag": "go/storage/azfile_b53cd6a3cc" } diff --git a/sdk/storage/azfile/directory/client.go b/sdk/storage/azfile/directory/client.go index 07a609261282..c72910cb23cc 100644 --- a/sdk/storage/azfile/directory/client.go +++ b/sdk/storage/azfile/directory/client.go @@ -28,6 +28,28 @@ type ClientOptions base.ClientOptions // Client represents a URL to the Azure Storage directory allowing you to manipulate its directories and files. type Client base.Client[generated.DirectoryClient] +// NewClient creates an instance of Client with the specified values. +// - directoryURL - the URL of the directory e.g. https://.file.core.windows.net/share/directory +// - cred - an Azure AD credential, typically obtained via the azidentity module +// - options - client options; pass nil to accept the default values +// +// Note that ClientOptions.FileRequestIntent is currently required for token authentication. +func NewClient(directoryURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) { + authPolicy := runtime.NewBearerTokenPolicy(cred, []string{shared.TokenScope}, nil) + conOptions := shared.GetClientOptions(options) + plOpts := runtime.PipelineOptions{ + PerRetry: []policy.Policy{authPolicy}, + } + base.SetPipelineOptions((*base.ClientOptions)(conOptions), &plOpts) + + azClient, err := azcore.NewClient(shared.DirectoryClient, exported.ModuleVersion, plOpts, &conOptions.ClientOptions) + if err != nil { + return nil, err + } + + return (*Client)(base.NewDirectoryClient(directoryURL, azClient, nil, (*base.ClientOptions)(conOptions))), nil +} + // NewClientWithNoCredential creates an instance of Client with the specified values. // This is used to anonymously access a directory or with a shared access signature (SAS) token. // - directoryURL - the URL of the directory e.g. https://.file.core.windows.net/share/directory? diff --git a/sdk/storage/azfile/directory/client_test.go b/sdk/storage/azfile/directory/client_test.go index 0403ffd2859a..b08168317720 100644 --- a/sdk/storage/azfile/directory/client_test.go +++ b/sdk/storage/azfile/directory/client_test.go @@ -1172,3 +1172,339 @@ func (d *DirectoryRecordedTestsSuite) TestDirectoryCreateWithTrailingSlash() { _, err = subDirClient.Create(context.Background(), nil) _require.NoError(err) } + +func (d *DirectoryRecordedTestsSuite) TestDirectoryCreateDeleteUsingOAuth() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + + options := &directory.ClientOptions{FileRequestIntent: to.Ptr(directory.ShareTokenIntentBackup)} + testcommon.SetClientOptions(d.T(), &options.ClientOptions) + dirClient, err := directory.NewClient(dirURL, cred, options) + _require.NoError(err) + + resp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(resp.ETag) + _require.NotNil(resp.RequestID) + _require.Equal(resp.LastModified.IsZero(), false) + _require.Equal(resp.FileCreationTime.IsZero(), false) + _require.Equal(resp.FileLastWriteTime.IsZero(), false) + _require.Equal(resp.FileChangeTime.IsZero(), false) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.NotNil(gResp.FileCreationTime) + _require.NotNil(gResp.FileLastWriteTime) + _require.NotNil(gResp.FilePermissionKey) + + dResp, err := dirClient.Delete(context.Background(), nil) + _require.NoError(err) + _require.Equal(dResp.Date.IsZero(), false) + _require.NotNil(dResp.RequestID) + _require.NotNil(dResp.Version) + + _, err = dirClient.GetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.ResourceNotFound) +} + +func (d *DirectoryRecordedTestsSuite) TestDirectorySetPropertiesUsingOAuth() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + + options := &directory.ClientOptions{FileRequestIntent: to.Ptr(directory.ShareTokenIntentBackup)} + testcommon.SetClientOptions(d.T(), &options.ClientOptions) + dirClient, err := directory.NewClient(dirURL, cred, options) + _require.NoError(err) + + cResp, err := dirClient.Create(context.Background(), nil) + _require.NoError(err) + _require.NotNil(cResp.FilePermissionKey) + + currTime, err := time.Parse(time.UnixDate, "Fri Mar 31 21:00:00 GMT 2023") + _require.NoError(err) + creationTime := currTime.Add(5 * time.Minute).Round(time.Microsecond) + lastWriteTime := currTime.Add(10 * time.Minute).Round(time.Millisecond) + + // Set the custom permissions + sResp, err := dirClient.SetProperties(context.Background(), &directory.SetPropertiesOptions{ + FileSMBProperties: &file.SMBProperties{ + Attributes: &file.NTFSFileAttributes{ + ReadOnly: true, + System: true, + }, + CreationTime: &creationTime, + LastWriteTime: &lastWriteTime, + }, + FilePermissions: &file.Permissions{ + Permission: &testcommon.SampleSDDL, + }, + }) + _require.NoError(err) + _require.NotNil(sResp.FileCreationTime) + _require.NotNil(sResp.FileLastWriteTime) + _require.NotNil(sResp.FilePermissionKey) + _require.NotEqual(*sResp.FilePermissionKey, *cResp.FilePermissionKey) + _require.Equal(*sResp.FileCreationTime, creationTime.UTC()) + _require.Equal(*sResp.FileLastWriteTime, lastWriteTime.UTC()) + + fileAttributes, err := file.ParseNTFSFileAttributes(sResp.FileAttributes) + _require.NoError(err) + _require.NotNil(fileAttributes) + _require.True(fileAttributes.ReadOnly) + _require.True(fileAttributes.System) + _require.True(fileAttributes.Directory) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.NotNil(gResp.FileCreationTime) + _require.NotNil(gResp.FileLastWriteTime) + _require.NotNil(gResp.FilePermissionKey) + _require.Equal(*gResp.FilePermissionKey, *sResp.FilePermissionKey) + _require.Equal(*gResp.FileCreationTime, *sResp.FileCreationTime) + _require.Equal(*gResp.FileLastWriteTime, *sResp.FileLastWriteTime) + _require.Equal(*gResp.FileAttributes, *sResp.FileAttributes) + + fileAttributes2, err := file.ParseNTFSFileAttributes(gResp.FileAttributes) + _require.NoError(err) + _require.NotNil(fileAttributes2) + _require.True(fileAttributes2.ReadOnly) + _require.True(fileAttributes2.System) + _require.True(fileAttributes2.Directory) + _require.EqualValues(fileAttributes2, fileAttributes) +} + +func (d *DirectoryRecordedTestsSuite) TestDirectorySetMetadataUsingOAuth() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + + options := &directory.ClientOptions{FileRequestIntent: to.Ptr(directory.ShareTokenIntentBackup)} + testcommon.SetClientOptions(d.T(), &options.ClientOptions) + dirClient, err := directory.NewClient(dirURL, cred, options) + _require.NoError(err) + + _, err = dirClient.Create(context.Background(), nil) + _require.NoError(err) + + md := map[string]*string{ + "Foo": to.Ptr("FooValuE"), + "Bar": to.Ptr("bArvaLue"), + } + + sResp, err := dirClient.SetMetadata(context.Background(), &directory.SetMetadataOptions{ + Metadata: md, + }) + _require.NoError(err) + _require.Equal(sResp.Date.IsZero(), false) + _require.NotNil(sResp.ETag) + _require.NotNil(sResp.RequestID) + _require.NotNil(sResp.Version) + _require.NotNil(sResp.IsServerEncrypted) + + gResp, err := dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(gResp.Date.IsZero(), false) + _require.NotNil(gResp.ETag) + _require.Equal(gResp.LastModified.IsZero(), false) + _require.NotNil(gResp.RequestID) + _require.NotNil(gResp.Version) + _require.NotNil(gResp.IsServerEncrypted) + _require.EqualValues(gResp.Metadata, md) +} + +func (d *DirectoryRecordedTestsSuite) TestDirectoryListHandlesUsingOAuth() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + + options := &directory.ClientOptions{FileRequestIntent: to.Ptr(directory.ShareTokenIntentBackup)} + testcommon.SetClientOptions(d.T(), &options.ClientOptions) + dirClient, err := directory.NewClient(dirURL, cred, options) + _require.NoError(err) + + _, err = dirClient.Create(context.Background(), nil) + _require.NoError(err) + + resp, err := dirClient.ListHandles(context.Background(), nil) + _require.NoError(err) + _require.Len(resp.Handles, 0) + _require.NotNil(resp.NextMarker) + _require.Equal(*resp.NextMarker, "") +} + +func (d *DirectoryRecordedTestsSuite) TestDirectoryForceCloseHandlesUsingOAuth() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + + options := &directory.ClientOptions{FileRequestIntent: to.Ptr(directory.ShareTokenIntentBackup)} + testcommon.SetClientOptions(d.T(), &options.ClientOptions) + dirClient, err := directory.NewClient(dirURL, cred, options) + _require.NoError(err) + + _, err = dirClient.Create(context.Background(), nil) + _require.NoError(err) + + resp, err := dirClient.ForceCloseHandles(context.Background(), "*", nil) + _require.NoError(err) + _require.EqualValues(*resp.NumberOfHandlesClosed, 0) + _require.EqualValues(*resp.NumberOfHandlesFailedToClose, 0) + _require.Nil(resp.Marker) +} + +func (d *DirectoryRecordedTestsSuite) TestDirectoryListUsingOAuth() { + _require := require.New(d.T()) + testName := d.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(d.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + dirName := testcommon.GenerateDirectoryName(testName) + dirURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + dirName + + options := &directory.ClientOptions{FileRequestIntent: to.Ptr(directory.ShareTokenIntentBackup)} + testcommon.SetClientOptions(d.T(), &options.ClientOptions) + dirClient, err := directory.NewClient(dirURL, cred, options) + _require.NoError(err) + + _, err = dirClient.Create(context.Background(), nil) + _require.NoError(err) + + fileName := testcommon.GenerateFileName(testName) + + // create directories + for i := 0; i < 10; i++ { + subDirClient := dirClient.NewSubdirectoryClient(dirName + fmt.Sprintf("%v", i)) + _, err = subDirClient.Create(context.Background(), nil) + _require.NoError(err) + } + + // create files + for i := 0; i < 5; i++ { + fileClient := dirClient.NewFileClient(fileName + fmt.Sprintf("%v", i)) + _, err = fileClient.Create(context.Background(), 2048, nil) + _require.NoError(err) + } + + subDirCtr, fileCtr := 0, 0 + pager := dirClient.NewListFilesAndDirectoriesPager(nil) + for pager.More() { + resp, err := pager.NextPage(context.Background()) + _require.NoError(err) + subDirCtr += len(resp.Segment.Directories) + fileCtr += len(resp.Segment.Files) + for _, dir := range resp.Segment.Directories { + _require.NotNil(dir.Name) + _require.Greater(len(*dir.Name), 0) + _require.NotNil(dir.ID) + _require.Nil(dir.Attributes) + _require.Nil(dir.PermissionKey) + _require.Nil(dir.Properties.ETag) + _require.Nil(dir.Properties.ChangeTime) + _require.Nil(dir.Properties.CreationTime) + _require.Nil(dir.Properties.ContentLength) + } + for _, f := range resp.Segment.Files { + _require.NotNil(f.Name) + _require.Greater(len(*f.Name), 0) + _require.NotNil(f.ID) + _require.Nil(f.Attributes) + _require.Nil(f.PermissionKey) + _require.Nil(f.Properties.ETag) + _require.Nil(f.Properties.ChangeTime) + _require.Nil(f.Properties.CreationTime) + _require.NotNil(f.Properties.ContentLength) + _require.Equal(*f.Properties.ContentLength, int64(2048)) + } + } + _require.Equal(subDirCtr, 10) + _require.Equal(fileCtr, 5) +} diff --git a/sdk/storage/azfile/file/client.go b/sdk/storage/azfile/file/client.go index 235babcd9653..bed80db6f1d2 100644 --- a/sdk/storage/azfile/file/client.go +++ b/sdk/storage/azfile/file/client.go @@ -34,6 +34,28 @@ type ClientOptions base.ClientOptions // Client represents a URL to the Azure Storage file. type Client base.Client[generated.FileClient] +// NewClient creates an instance of Client with the specified values. +// - fileURL - the URL of the file e.g. https://.file.core.windows.net/share/directoryPath/file +// - cred - an Azure AD credential, typically obtained via the azidentity module +// - options - client options; pass nil to accept the default values +// +// Note that ClientOptions.FileRequestIntent is currently required for token authentication. +func NewClient(fileURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) { + authPolicy := runtime.NewBearerTokenPolicy(cred, []string{shared.TokenScope}, nil) + conOptions := shared.GetClientOptions(options) + plOpts := runtime.PipelineOptions{ + PerRetry: []policy.Policy{authPolicy}, + } + base.SetPipelineOptions((*base.ClientOptions)(conOptions), &plOpts) + + azClient, err := azcore.NewClient(shared.FileClient, exported.ModuleVersion, plOpts, &conOptions.ClientOptions) + if err != nil { + return nil, err + } + + return (*Client)(base.NewFileClient(fileURL, azClient, nil, (*base.ClientOptions)(conOptions))), nil +} + // NewClientWithNoCredential creates an instance of Client with the specified values. // This is used to anonymously access a file or with a shared access signature (SAS) token. // - fileURL - the URL of the file e.g. https://.file.core.windows.net/share/directoryPath/file? diff --git a/sdk/storage/azfile/file/client_test.go b/sdk/storage/azfile/file/client_test.go index 6b86ae745325..1e552d43989e 100644 --- a/sdk/storage/azfile/file/client_test.go +++ b/sdk/storage/azfile/file/client_test.go @@ -488,7 +488,6 @@ func (f *FileRecordedTestsSuite) TestFileGetSetPropertiesDefault() { }) _require.NoError(err) - // get properties on the share snapshot getResp, err := fClient.GetProperties(context.Background(), nil) _require.NoError(err) _require.Equal(setResp.LastModified.IsZero(), false) @@ -3152,4 +3151,240 @@ func (f *FileRecordedTestsSuite) TestFileForceCloseHandlesDefault() { _require.Nil(resp.Marker) } +func (f *FileRecordedTestsSuite) TestFileCreateDeleteUsingOAuth() { + _require := require.New(f.T()) + testName := f.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(f.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + fileName := testcommon.GenerateFileName(testName) + fileURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + fileName + + options := &file.ClientOptions{FileRequestIntent: to.Ptr(file.ShareTokenIntentBackup)} + testcommon.SetClientOptions(f.T(), &options.ClientOptions) + fileClient, err := file.NewClient(fileURL, cred, options) + _require.NoError(err) + + resp, err := fileClient.Create(context.Background(), 2048, nil) + _require.NoError(err) + _require.NotNil(resp.ETag) + _require.NotNil(resp.RequestID) + _require.Equal(resp.LastModified.IsZero(), false) + _require.Equal(resp.FileCreationTime.IsZero(), false) + _require.Equal(resp.FileLastWriteTime.IsZero(), false) + _require.Equal(resp.FileChangeTime.IsZero(), false) + + gResp, err := fileClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.NotNil(gResp.FileCreationTime) + _require.NotNil(gResp.FileLastWriteTime) + _require.NotNil(gResp.FilePermissionKey) + _require.Equal(*gResp.ContentLength, int64(2048)) + + dResp, err := fileClient.Delete(context.Background(), nil) + _require.NoError(err) + _require.Equal(dResp.Date.IsZero(), false) + _require.NotNil(dResp.RequestID) + _require.NotNil(dResp.Version) + + _, err = fileClient.GetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.ResourceNotFound) +} + +func (f *FileRecordedTestsSuite) TestFileGetSetPropertiesUsingOAuth() { + _require := require.New(f.T()) + testName := f.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(f.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + fileName := testcommon.GenerateFileName(testName) + fileURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + fileName + + clOptions := &file.ClientOptions{FileRequestIntent: to.Ptr(file.ShareTokenIntentBackup)} + testcommon.SetClientOptions(f.T(), &clOptions.ClientOptions) + fClient, err := file.NewClient(fileURL, cred, clOptions) + _require.NoError(err) + + _, err = fClient.Create(context.Background(), 2048, nil) + _require.NoError(err) + + md5Str := "MDAwMDAwMDA=" + testMd5 := []byte(md5Str) + + options := &file.SetHTTPHeadersOptions{ + Permissions: &file.Permissions{Permission: &testcommon.SampleSDDL}, + SMBProperties: &file.SMBProperties{ + Attributes: &file.NTFSFileAttributes{Hidden: true, ReadOnly: true}, + }, + HTTPHeaders: &file.HTTPHeaders{ + ContentType: to.Ptr("text/html"), + ContentEncoding: to.Ptr("gzip"), + ContentLanguage: to.Ptr("en"), + ContentMD5: testMd5, + CacheControl: to.Ptr("no-transform"), + ContentDisposition: to.Ptr("attachment"), + }, + } + setResp, err := fClient.SetHTTPHeaders(context.Background(), options) + _require.NoError(err) + _require.NotNil(setResp.ETag) + _require.Equal(setResp.LastModified.IsZero(), false) + _require.NotNil(setResp.RequestID) + _require.NotNil(setResp.Version) + _require.Equal(setResp.Date.IsZero(), false) + _require.NotNil(setResp.IsServerEncrypted) + + fileAttributes, err := file.ParseNTFSFileAttributes(setResp.FileAttributes) + _require.NoError(err) + _require.NotNil(fileAttributes) + _require.True(fileAttributes.Hidden) + _require.True(fileAttributes.ReadOnly) + + getResp, err := fClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(setResp.LastModified.IsZero(), false) + _require.Equal(*getResp.FileType, "File") + + _require.EqualValues(getResp.ContentType, options.HTTPHeaders.ContentType) + _require.EqualValues(getResp.ContentEncoding, options.HTTPHeaders.ContentEncoding) + _require.EqualValues(getResp.ContentLanguage, options.HTTPHeaders.ContentLanguage) + _require.EqualValues(getResp.ContentMD5, options.HTTPHeaders.ContentMD5) + _require.EqualValues(getResp.CacheControl, options.HTTPHeaders.CacheControl) + _require.EqualValues(getResp.ContentDisposition, options.HTTPHeaders.ContentDisposition) + _require.Equal(*getResp.ContentLength, int64(2048)) + _require.NotEqual(getResp.FilePermissionKey, "") + + fileAttributes2, err := file.ParseNTFSFileAttributes(getResp.FileAttributes) + _require.NoError(err) + _require.NotNil(fileAttributes2) + _require.True(fileAttributes2.Hidden) + _require.True(fileAttributes2.ReadOnly) + _require.EqualValues(fileAttributes, fileAttributes2) + _require.NotNil(getResp.ETag) + _require.NotNil(getResp.RequestID) + _require.NotNil(getResp.Version) + _require.Equal(getResp.Date.IsZero(), false) + _require.NotNil(getResp.IsServerEncrypted) +} + +func (f *FileRecordedTestsSuite) TestFileSetMetadataUsingOAuth() { + _require := require.New(f.T()) + testName := f.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(f.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + fileName := testcommon.GenerateFileName(testName) + fileURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + fileName + + options := &file.ClientOptions{FileRequestIntent: to.Ptr(file.ShareTokenIntentBackup)} + testcommon.SetClientOptions(f.T(), &options.ClientOptions) + fileClient, err := file.NewClient(fileURL, cred, options) + _require.NoError(err) + + _, err = fileClient.Create(context.Background(), 2048, nil) + _require.NoError(err) + + metadata := map[string]*string{ + "Foo": to.Ptr("Foovalue"), + "Bar": to.Ptr("Barvalue"), + } + _, err = fileClient.SetMetadata(context.Background(), &file.SetMetadataOptions{ + Metadata: metadata, + }) + _require.NoError(err) + + getResp, err := fileClient.GetProperties(context.Background(), nil) + _require.NoError(err) + _require.Equal(*getResp.ContentLength, int64(2048)) + _require.EqualValues(getResp.Metadata, metadata) +} + +func (f *FileRecordedTestsSuite) TestFileUploadClearListRangeUsingOAuth() { + _require := require.New(f.T()) + testName := f.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(f.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + fileName := testcommon.GenerateFileName(testName) + fileURL := "https://" + accountName + ".file.core.windows.net/" + shareName + "/" + fileName + + options := &file.ClientOptions{FileRequestIntent: to.Ptr(file.ShareTokenIntentBackup)} + testcommon.SetClientOptions(f.T(), &options.ClientOptions) + fileClient, err := file.NewClient(fileURL, cred, options) + _require.NoError(err) + + _, err = fileClient.Create(context.Background(), 2048, nil) + _require.NoError(err) + + contentSize := 1024 * 2 // 2KB + contentR, contentD := testcommon.GenerateData(contentSize) + md5Value := md5.Sum(contentD) + contentMD5 := md5Value[:] + + uResp, err := fileClient.UploadRange(context.Background(), 0, contentR, &file.UploadRangeOptions{ + TransactionalValidation: file.TransferValidationTypeMD5(contentMD5), + }) + _require.NoError(err) + _require.NotNil(uResp.ContentMD5) + _require.EqualValues(uResp.ContentMD5, contentMD5) + + rangeList, err := fileClient.GetRangeList(context.Background(), nil) + _require.NoError(err) + _require.Len(rangeList.Ranges, 1) + _require.EqualValues(*rangeList.Ranges[0], file.ShareFileRange{Start: to.Ptr(int64(0)), End: to.Ptr(int64(contentSize - 1))}) + + cResp, err := fileClient.ClearRange(context.Background(), file.HTTPRange{Offset: 0, Count: int64(contentSize)}, nil) + _require.NoError(err) + _require.Nil(cResp.ContentMD5) + + rangeList2, err := fileClient.GetRangeList(context.Background(), nil) + _require.NoError(err) + _require.Len(rangeList2.Ranges, 0) +} + // TODO: Add tests for retry header options diff --git a/sdk/storage/azfile/fileerror/error_codes.go b/sdk/storage/azfile/fileerror/error_codes.go index c897c0953828..5b6070f0a990 100644 --- a/sdk/storage/azfile/fileerror/error_codes.go +++ b/sdk/storage/azfile/fileerror/error_codes.go @@ -34,71 +34,72 @@ func HasCode(err error, codes ...Code) bool { type Code = generated.StorageErrorCode const ( - AccountAlreadyExists Code = "AccountAlreadyExists" - AccountBeingCreated Code = "AccountBeingCreated" - AccountIsDisabled Code = "AccountIsDisabled" - AuthenticationFailed Code = "AuthenticationFailed" - AuthorizationFailure Code = "AuthorizationFailure" - AuthorizationPermissionMismatch Code = "AuthorizationPermissionMismatch" - AuthorizationProtocolMismatch Code = "AuthorizationProtocolMismatch" - AuthorizationResourceTypeMismatch Code = "AuthorizationResourceTypeMismatch" - AuthorizationServiceMismatch Code = "AuthorizationServiceMismatch" - AuthorizationSourceIPMismatch Code = "AuthorizationSourceIPMismatch" - CannotDeleteFileOrDirectory Code = "CannotDeleteFileOrDirectory" - ClientCacheFlushDelay Code = "ClientCacheFlushDelay" - ConditionHeadersNotSupported Code = "ConditionHeadersNotSupported" - ConditionNotMet Code = "ConditionNotMet" - DeletePending Code = "DeletePending" - DirectoryNotEmpty Code = "DirectoryNotEmpty" - EmptyMetadataKey Code = "EmptyMetadataKey" - FeatureVersionMismatch Code = "FeatureVersionMismatch" - FileLockConflict Code = "FileLockConflict" - InsufficientAccountPermissions Code = "InsufficientAccountPermissions" - InternalError Code = "InternalError" - InvalidAuthenticationInfo Code = "InvalidAuthenticationInfo" - InvalidFileOrDirectoryPathName Code = "InvalidFileOrDirectoryPathName" - InvalidHTTPVerb Code = "InvalidHttpVerb" - InvalidHeaderValue Code = "InvalidHeaderValue" - InvalidInput Code = "InvalidInput" - InvalidMD5 Code = "InvalidMd5" - InvalidMetadata Code = "InvalidMetadata" - InvalidQueryParameterValue Code = "InvalidQueryParameterValue" - InvalidRange Code = "InvalidRange" - InvalidResourceName Code = "InvalidResourceName" - InvalidURI Code = "InvalidUri" - InvalidXMLDocument Code = "InvalidXmlDocument" - InvalidXMLNodeValue Code = "InvalidXmlNodeValue" - MD5Mismatch Code = "Md5Mismatch" - MetadataTooLarge Code = "MetadataTooLarge" - MissingContentLengthHeader Code = "MissingContentLengthHeader" - MissingRequiredHeader Code = "MissingRequiredHeader" - MissingRequiredQueryParameter Code = "MissingRequiredQueryParameter" - MissingRequiredXMLNode Code = "MissingRequiredXmlNode" - MultipleConditionHeadersNotSupported Code = "MultipleConditionHeadersNotSupported" - OperationTimedOut Code = "OperationTimedOut" - OutOfRangeInput Code = "OutOfRangeInput" - OutOfRangeQueryParameterValue Code = "OutOfRangeQueryParameterValue" - ParentNotFound Code = "ParentNotFound" - ReadOnlyAttribute Code = "ReadOnlyAttribute" - RequestBodyTooLarge Code = "RequestBodyTooLarge" - RequestURLFailedToParse Code = "RequestUrlFailedToParse" - ResourceAlreadyExists Code = "ResourceAlreadyExists" - ResourceNotFound Code = "ResourceNotFound" - ResourceTypeMismatch Code = "ResourceTypeMismatch" - ServerBusy Code = "ServerBusy" - ShareAlreadyExists Code = "ShareAlreadyExists" - ShareBeingDeleted Code = "ShareBeingDeleted" - ShareDisabled Code = "ShareDisabled" - ShareHasSnapshots Code = "ShareHasSnapshots" - ShareNotFound Code = "ShareNotFound" - ShareSnapshotCountExceeded Code = "ShareSnapshotCountExceeded" - ShareSnapshotInProgress Code = "ShareSnapshotInProgress" - ShareSnapshotOperationNotSupported Code = "ShareSnapshotOperationNotSupported" - SharingViolation Code = "SharingViolation" - UnsupportedHTTPVerb Code = "UnsupportedHttpVerb" - UnsupportedHeader Code = "UnsupportedHeader" - UnsupportedQueryParameter Code = "UnsupportedQueryParameter" - UnsupportedXMLNode Code = "UnsupportedXmlNode" + AccountAlreadyExists Code = "AccountAlreadyExists" + AccountBeingCreated Code = "AccountBeingCreated" + AccountIsDisabled Code = "AccountIsDisabled" + AuthenticationFailed Code = "AuthenticationFailed" + AuthorizationFailure Code = "AuthorizationFailure" + AuthorizationPermissionMismatch Code = "AuthorizationPermissionMismatch" + AuthorizationProtocolMismatch Code = "AuthorizationProtocolMismatch" + AuthorizationResourceTypeMismatch Code = "AuthorizationResourceTypeMismatch" + AuthorizationServiceMismatch Code = "AuthorizationServiceMismatch" + AuthorizationSourceIPMismatch Code = "AuthorizationSourceIPMismatch" + CannotDeleteFileOrDirectory Code = "CannotDeleteFileOrDirectory" + ClientCacheFlushDelay Code = "ClientCacheFlushDelay" + ConditionHeadersNotSupported Code = "ConditionHeadersNotSupported" + ConditionNotMet Code = "ConditionNotMet" + DeletePending Code = "DeletePending" + DirectoryNotEmpty Code = "DirectoryNotEmpty" + EmptyMetadataKey Code = "EmptyMetadataKey" + FeatureVersionMismatch Code = "FeatureVersionMismatch" + FileLockConflict Code = "FileLockConflict" + InsufficientAccountPermissions Code = "InsufficientAccountPermissions" + InternalError Code = "InternalError" + InvalidAuthenticationInfo Code = "InvalidAuthenticationInfo" + InvalidFileOrDirectoryPathName Code = "InvalidFileOrDirectoryPathName" + InvalidHTTPVerb Code = "InvalidHttpVerb" + InvalidHeaderValue Code = "InvalidHeaderValue" + InvalidInput Code = "InvalidInput" + InvalidMD5 Code = "InvalidMd5" + InvalidMetadata Code = "InvalidMetadata" + InvalidQueryParameterValue Code = "InvalidQueryParameterValue" + InvalidRange Code = "InvalidRange" + InvalidResourceName Code = "InvalidResourceName" + InvalidURI Code = "InvalidUri" + InvalidXMLDocument Code = "InvalidXmlDocument" + InvalidXMLNodeValue Code = "InvalidXmlNodeValue" + MD5Mismatch Code = "Md5Mismatch" + MetadataTooLarge Code = "MetadataTooLarge" + MissingContentLengthHeader Code = "MissingContentLengthHeader" + MissingRequiredHeader Code = "MissingRequiredHeader" + MissingRequiredQueryParameter Code = "MissingRequiredQueryParameter" + MissingRequiredXMLNode Code = "MissingRequiredXmlNode" + MultipleConditionHeadersNotSupported Code = "MultipleConditionHeadersNotSupported" + OperationTimedOut Code = "OperationTimedOut" + OutOfRangeInput Code = "OutOfRangeInput" + OutOfRangeQueryParameterValue Code = "OutOfRangeQueryParameterValue" + ParentNotFound Code = "ParentNotFound" + ReadOnlyAttribute Code = "ReadOnlyAttribute" + RequestBodyTooLarge Code = "RequestBodyTooLarge" + RequestURLFailedToParse Code = "RequestUrlFailedToParse" + ResourceAlreadyExists Code = "ResourceAlreadyExists" + ResourceNotFound Code = "ResourceNotFound" + ResourceTypeMismatch Code = "ResourceTypeMismatch" + ServerBusy Code = "ServerBusy" + ShareAlreadyExists Code = "ShareAlreadyExists" + ShareBeingDeleted Code = "ShareBeingDeleted" + ShareDisabled Code = "ShareDisabled" + ShareHasSnapshots Code = "ShareHasSnapshots" + ShareNotFound Code = "ShareNotFound" + ShareSnapshotCountExceeded Code = "ShareSnapshotCountExceeded" + ShareSnapshotInProgress Code = "ShareSnapshotInProgress" + ShareSnapshotOperationNotSupported Code = "ShareSnapshotOperationNotSupported" + SharingViolation Code = "SharingViolation" + UnsupportedHTTPVerb Code = "UnsupportedHttpVerb" + UnsupportedHeader Code = "UnsupportedHeader" + UnsupportedQueryParameter Code = "UnsupportedQueryParameter" + UnsupportedXMLNode Code = "UnsupportedXmlNode" + FileOAuthManagementApiRestrictedToSrp Code = "FileOAuthManagementApiRestrictedToSrp" ) var ( diff --git a/sdk/storage/azfile/service/client.go b/sdk/storage/azfile/service/client.go index 372313f78eb5..2811623b7673 100644 --- a/sdk/storage/azfile/service/client.go +++ b/sdk/storage/azfile/service/client.go @@ -30,6 +30,30 @@ type ClientOptions base.ClientOptions // Client represents a URL to the Azure File Storage service allowing you to manipulate file shares. type Client base.Client[generated.ServiceClient] +// NewClient creates an instance of Client with the specified values. +// - serviceURL - the URL of the storage account e.g. https://.file.core.windows.net/ +// - cred - an Azure AD credential, typically obtained via the azidentity module +// - options - client options; pass nil to accept the default values +// +// Note that service-level operations do not support token credential authentication. +// This constructor exists to allow the construction of a share.Client that has token credential authentication. +// Also note that ClientOptions.FileRequestIntent is currently required for token authentication. +func NewClient(serviceURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) { + authPolicy := runtime.NewBearerTokenPolicy(cred, []string{shared.TokenScope}, nil) + conOptions := shared.GetClientOptions(options) + plOpts := runtime.PipelineOptions{ + PerRetry: []policy.Policy{authPolicy}, + } + base.SetPipelineOptions((*base.ClientOptions)(conOptions), &plOpts) + + azClient, err := azcore.NewClient(shared.ServiceClient, exported.ModuleVersion, plOpts, &conOptions.ClientOptions) + if err != nil { + return nil, err + } + + return (*Client)(base.NewServiceClient(serviceURL, azClient, nil, (*base.ClientOptions)(conOptions))), nil +} + // NewClientWithNoCredential creates an instance of Client with the specified values. // This is used to anonymously access a storage account or with a shared access signature (SAS) token. // - serviceURL - the URL of the storage account e.g. https://.file.core.windows.net/? diff --git a/sdk/storage/azfile/service/client_test.go b/sdk/storage/azfile/service/client_test.go index d9c3642c4628..8f0b3982be48 100644 --- a/sdk/storage/azfile/service/client_test.go +++ b/sdk/storage/azfile/service/client_test.go @@ -452,3 +452,67 @@ func (s *ServiceRecordedTestsSuite) TestServiceCreateDeleteRestoreShare() { } _require.Equal(sharesCnt, 1) } + +func (s *ServiceRecordedTestsSuite) TestServiceOAuthNegative() { + _require := require.New(s.T()) + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + options := &service.ClientOptions{FileRequestIntent: to.Ptr(service.ShareTokenIntentBackup)} + testcommon.SetClientOptions(s.T(), &options.ClientOptions) + svcClient, err := service.NewClient("https://"+accountName+".file.core.windows.net/", cred, options) + _require.NoError(err) + + // service-level operations are not supported using token credential authentication. + _, err = svcClient.GetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.FileOAuthManagementApiRestrictedToSrp) + + _, err = svcClient.SetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.FileOAuthManagementApiRestrictedToSrp) + + pager := svcClient.NewListSharesPager(nil) + _, err = pager.NextPage(context.Background()) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.FileOAuthManagementApiRestrictedToSrp) +} + +func (s *ServiceRecordedTestsSuite) TestServiceCreateDeleteDirOAuth() { + _require := require.New(s.T()) + testName := s.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + // create service client using token credential + options := &service.ClientOptions{FileRequestIntent: to.Ptr(service.ShareTokenIntentBackup)} + testcommon.SetClientOptions(s.T(), &options.ClientOptions) + svcClientOAuth, err := service.NewClient("https://"+accountName+".file.core.windows.net/", cred, options) + _require.NoError(err) + + dirClient := svcClientOAuth.NewShareClient(shareName).NewDirectoryClient(testcommon.GenerateDirectoryName(testName)) + + _, err = dirClient.Create(context.Background(), nil) + _require.NoError(err) + + _, err = dirClient.GetProperties(context.Background(), nil) + _require.NoError(err) + + _, err = dirClient.Delete(context.Background(), nil) + _require.NoError(err) +} diff --git a/sdk/storage/azfile/share/client.go b/sdk/storage/azfile/share/client.go index 4f797404cfbb..8810141fcce0 100644 --- a/sdk/storage/azfile/share/client.go +++ b/sdk/storage/azfile/share/client.go @@ -30,6 +30,29 @@ type ClientOptions base.ClientOptions // Client represents a URL to the Azure Storage share allowing you to manipulate its directories and files. type Client base.Client[generated.ShareClient] +// NewClient creates an instance of Client with the specified values. +// - shareURL - the URL of the share e.g. https://.file.core.windows.net/share +// - cred - an Azure AD credential, typically obtained via the azidentity module +// - options - client options; pass nil to accept the default values +// +// Note that the only share-level operations that support token credential authentication are CreatePermission and GetPermission. +// Also note that ClientOptions.FileRequestIntent is currently required for token authentication. +func NewClient(shareURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) { + authPolicy := runtime.NewBearerTokenPolicy(cred, []string{shared.TokenScope}, nil) + conOptions := shared.GetClientOptions(options) + plOpts := runtime.PipelineOptions{ + PerRetry: []policy.Policy{authPolicy}, + } + base.SetPipelineOptions((*base.ClientOptions)(conOptions), &plOpts) + + azClient, err := azcore.NewClient(shared.ShareClient, exported.ModuleVersion, plOpts, &conOptions.ClientOptions) + if err != nil { + return nil, err + } + + return (*Client)(base.NewShareClient(shareURL, azClient, nil, (*base.ClientOptions)(conOptions))), nil +} + // NewClientWithNoCredential creates an instance of Client with the specified values. // This is used to anonymously access a share or with a shared access signature (SAS) token. // - shareURL - the URL of the share e.g. https://.file.core.windows.net/share? diff --git a/sdk/storage/azfile/share/client_test.go b/sdk/storage/azfile/share/client_test.go index 44940a537d27..9e1cc6976bb8 100644 --- a/sdk/storage/azfile/share/client_test.go +++ b/sdk/storage/azfile/share/client_test.go @@ -1458,3 +1458,70 @@ func (s *ShareRecordedTestsSuite) TestSASShareClientSignNegative() { _, err = shareClient.GetSASURL(sas.SharePermissions{}, expiry, nil) _require.Equal(err.Error(), "service SAS is missing at least one of these: ExpiryTime or Permissions") } + +func (s *ShareRecordedTestsSuite) TestShareOAuthNegative() { + _require := require.New(s.T()) + testName := s.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + options := &share.ClientOptions{FileRequestIntent: to.Ptr(share.TokenIntentBackup)} + testcommon.SetClientOptions(s.T(), &options.ClientOptions) + shareClient, err := share.NewClient("https://"+accountName+".file.core.windows.net/"+shareName, cred, options) + _require.NoError(err) + + _, err = shareClient.Create(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.FileOAuthManagementApiRestrictedToSrp) + + _, err = shareClient.GetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.FileOAuthManagementApiRestrictedToSrp) + + _, err = shareClient.SetProperties(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.FileOAuthManagementApiRestrictedToSrp) + + _, err = shareClient.Delete(context.Background(), nil) + _require.Error(err) + testcommon.ValidateFileErrorCode(_require, err, fileerror.FileOAuthManagementApiRestrictedToSrp) +} + +func (s *ShareRecordedTestsSuite) TestShareCreateAndGetPermissionOAuth() { + _require := require.New(s.T()) + testName := s.T().Name() + + accountName, _ := testcommon.GetGenericAccountInfo(testcommon.TestAccountDefault) + _require.Greater(len(accountName), 0) + + cred, err := testcommon.GetGenericTokenCredential() + _require.NoError(err) + + svcClient, err := testcommon.GetServiceClient(s.T(), testcommon.TestAccountDefault, nil) + _require.NoError(err) + + shareName := testcommon.GenerateShareName(testName) + shareClient := testcommon.CreateNewShare(context.Background(), _require, shareName, svcClient) + defer testcommon.DeleteShare(context.Background(), _require, shareClient) + + options := &share.ClientOptions{FileRequestIntent: to.Ptr(share.TokenIntentBackup)} + testcommon.SetClientOptions(s.T(), &options.ClientOptions) + shareClientOAuth, err := share.NewClient("https://"+accountName+".file.core.windows.net/"+shareName, cred, options) + _require.NoError(err) + + // Create a permission and check that it's not empty. + createResp, err := shareClientOAuth.CreatePermission(context.Background(), testcommon.SampleSDDL, nil) + _require.NoError(err) + _require.NotNil(createResp.FilePermissionKey) + _require.NotEmpty(*createResp.FilePermissionKey) + + getResp, err := shareClientOAuth.GetPermission(context.Background(), *createResp.FilePermissionKey, nil) + _require.NoError(err) + _require.NotNil(getResp.Permission) + _require.NotEmpty(*getResp.Permission) +} diff --git a/sdk/storage/azfile/test-resources.json b/sdk/storage/azfile/test-resources.json index c6259f7ab02f..912321ecd276 100644 --- a/sdk/storage/azfile/test-resources.json +++ b/sdk/storage/azfile/test-resources.json @@ -25,6 +25,7 @@ "blobDataContributorRoleId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe')]", "contributorRoleId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c')]", "blobDataOwnerRoleId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/b7e6dc6d-f1e8-4753-8033-0f276bb0955b')]", + "fileElevatedContributorCustomRoleId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/9276e164-dbbe-4f3f-b4db-cfdd87eebbe9')]", "primaryAccountName": "[concat(parameters('baseName'), 'prim')]", "immutableAccountName": "[concat(parameters('baseName'), 'imm')]", "primaryEncryptionScopeName": "encryptionScope", @@ -85,6 +86,15 @@ "principalId": "[parameters('testApplicationOid')]" } }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "[variables('authorizationApiVersion')]", + "name": "[guid(concat('fileElevatedContributorCustomRoleId', resourceGroup().id))]", + "properties": { + "roleDefinitionId": "[variables('fileElevatedContributorCustomRoleId')]", + "principalId": "[parameters('testApplicationOid')]" + } + }, { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "[variables('mgmtApiVersion')]",