Skip to content

Commit

Permalink
[Storage] Use @azure/core-http in @azure/storage-* libraries to suppo…
Browse files Browse the repository at this point in the history
…rt @azure/identity (#3853)

* Use @azure/core-http in @azure/storage-queue

* Use @azure/core-http in @azure/storage-file

* Use @azure/core-http in @azure/storage-blob

* Rename TokenCredential to RawTokenCredential

* Migrate over fixes from @azure/ms-rest-js

These changes come from the following two commits:

Azure/ms-rest-js@bbeb122
Azure/ms-rest-js@d7ed995

* Add tests and samples for using identity with storage-blob

* Add tests and samples for using identity with storage-queue

* Make isTokenCredential more resilient

* Remove TokenCredential usage from storage-file as it isn't supported yet

* Minor tweaks for PR feedback

* TokenCredential -> RawTokenCredential in samples

* Update BreakingChanges.md for storage-blob and storage-queue

* Improve TokenCredential comments in basic samples

* Fix type of 'include' field on options types

* Move storage-queue TokenCredential tests to node-only
  • Loading branch information
daviwil authored Jun 24, 2019
1 parent fd9497e commit b2ac1b7
Show file tree
Hide file tree
Showing 170 changed files with 3,859 additions and 4,749 deletions.
9 changes: 9 additions & 0 deletions sdk/core/core-http/lib/axiosHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,15 @@ export class AxiosHttpClient implements HttpClient {
config.httpAgent = agent.agent;
}
}
// This hack is still required with 0.19.0 version of axios since axios tries to merge the
// Content-Type header from it's config["<method name>"] where the method name is lower-case,
// into the request header. It could be possible that the Content-Type header is not present
// in the original request and this would create problems while creating the signature for
// storage data plane sdks.
axios.interceptors.request.use((config: AxiosRequestConfig) => ({
...config,
method: (config.method as Method) && (config.method as Method).toUpperCase() as Method
}));

res = await axios.request(config);
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion sdk/core/core-http/lib/credentials/tokenCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,5 @@ export interface AccessToken {
* @param credential The assumed TokenCredential to be tested.
*/
export function isTokenCredential(credential: any): credential is TokenCredential {
return "getToken" in credential;
return credential && typeof credential.getToken === "function";
}
6 changes: 5 additions & 1 deletion sdk/core/core-http/test/mockHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import xhrMock, { proxy } from "xhr-mock";
import MockAdapter from "axios-mock-adapter";
import { isNode, HttpMethods } from "../lib/coreHttp";
import { AxiosRequestConfig, AxiosInstance } from "axios";
import { AxiosRequestConfig, AxiosInstance, Method } from "axios";

export type UrlFilter = string | RegExp;

Expand Down Expand Up @@ -41,6 +41,10 @@ class NodeHttpMock implements HttpMockFacade {
throw new Error("Axios instance cannot be undefined");
}
this._mockAdapter = new MockAdapter(axiosInstance);
axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => ({
...config,
method: (config.method as Method) && (config.method as Method).toLowerCase() as Method
}));
}

setup(): void {
Expand Down
5 changes: 4 additions & 1 deletion sdk/storage/storage-blob/BreakingChanges.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Breaking Changes

2019.6 Version 11.0.0-preview.1
* `TokenCredential` has been renamed to `RawTokenCredential` to make way for the new `@azure/identity` library's `TokenCredential` interface.

2018.12 10.3.0

* Updated convenience layer methods enum type parameters into typescript union types, this will help reducing bundle footprint.
Expand All @@ -21,4 +24,4 @@
* `String.prototype.startsWith`
* `String.prototype.endsWith`
* `String.prototype.repeat`
* `String.prototype.includes`
* `String.prototype.includes`
3 changes: 2 additions & 1 deletion sdk/storage/storage-blob/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,13 @@
"homepage": "https://github.com/Azure/azure-sdk-for-js#readme",
"sideEffects": false,
"dependencies": {
"@azure/ms-rest-js": "^1.2.6",
"@azure/core-http": "^1.2.6",
"@azure/core-paging": "^1.0.0",
"events": "^3.0.0",
"tslib": "^1.9.3"
},
"devDependencies": {
"@azure/identity": "^0.1.0",
"@microsoft/api-extractor": "^7.1.5",
"@types/dotenv": "^6.1.0",
"@types/fs-extra": "~7.0.0",
Expand Down
3 changes: 1 addition & 2 deletions sdk/storage/storage-blob/rollup.base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const depNames = Object.keys(pkg.dependencies);
const production = process.env.NODE_ENV === "production";

export function nodeConfig(test = false) {
const externalNodeBuiltins = ["@azure/ms-rest-js", "crypto", "fs", "events", "os", "stream"];
const externalNodeBuiltins = ["@azure/core-http", "crypto", "fs", "events", "os", "stream"];
const baseConfig = {
input: "dist-esm/src/index.js",
external: depNames.concat(externalNodeBuiltins),
Expand Down Expand Up @@ -70,7 +70,6 @@ export function nodeConfig(test = false) {
export function browserConfig(test = false, production = false) {
const baseConfig = {
input: "dist-esm/src/index.browser.js",
external: ["ms-rest-js"],
output: {
file: "browser/azure-storage-blob.js",
banner: banner,
Expand Down
4 changes: 4 additions & 0 deletions sdk/storage/storage-blob/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ npm install @azure/storage-blob
- Note down the "AccountName", "AccountKey" obtained at **Access keys** and "AccountSAS" from **Shared access signature** under **Settings** tab.
Before running any of the samples, update with the credentials you have noted down above.

### Authenticating with Azure Active Directory

If you have [registered an application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) with an Azure Active Directory tenant, you can [assign it to an RBAC role](https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad) in your Azure Storage account. This enables you to use the Azure.Identity library to authenticate with Azure Storage as shown in the [azureAdAuth.ts sample](./samples/azureAdAuth.ts).

## Running Samples

- Change `"../.."` to `"@azure/storage-blob"` in the samples in order to import the published package instead of using source code.
Expand Down
44 changes: 44 additions & 0 deletions sdk/storage/storage-blob/samples/javascript/azureAdAuth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Setup: Enter your Azure Active Directory credentials as described in main()
*/

const { BlobServiceClient } = require("../.."); // Change to "@azure/storage-blob" in your package
const { DefaultAzureCredential } = require("@azure/identity");

async function main() {
// Enter your storage account name
const account = "";

// DefaultAzureCredential will first look for Azure Active Directory (AAD)
// client secret credentials in the following environment variables:
//
// - AZURE_TENANT_ID: The ID of your AAD tenant
// - AZURE_CLIENT_ID: The ID of your AAD app registration (client)
// - AZURE_CLIENT_SECRET: The client secret for your AAD app registration
//
// If those environment variables aren't found and your application is deployed
// to an Azure VM or App Service instance, the managed service identity endpoint
// will be used as a fallback authentication source.
const defaultAzureCredential = new DefaultAzureCredential();

const blobServiceClient = new BlobServiceClient(
`https://${account}.blob.core.windows.net`,
defaultAzureCredential,
);

// Create a container
const containerName = `newcontainer${new Date().getTime()}`;
const createContainerResponse = await blobServiceClient
.createContainerClient(containerName)
.create();
console.log(`Created container ${containerName} successfully`, createContainerResponse.requestId);
}

// An async method returns a Promise object, which is compatible with then().catch() coding style.
main()
.then(() => {
console.log("Successfully executed the sample.");
})
.catch((err) => {
console.log(err.message);
});
9 changes: 6 additions & 3 deletions sdk/storage/storage-blob/samples/javascript/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const {
newPipeline,
SharedKeyCredential,
AnonymousCredential,
TokenCredential
RawTokenCredential
} = require("../.."); // Change to "@azure/storage-blob" in your package

async function main() {
Expand All @@ -18,8 +18,11 @@ async function main() {
// Use SharedKeyCredential with storage account and account key
const sharedKeyCredential = new SharedKeyCredential(account, accountKey);

// Use TokenCredential with OAuth token
const tokenCredential = new TokenCredential("token");
// Use RawTokenCredential with OAuth token. You can find more
// TokenCredential implementations in the @azure/identity library
// to use client secrets, certificates, or managed identities for
// authentication.
const tokenCredential = new RawTokenCredential("token");
tokenCredential.token = "renewedToken"; // Renew the token by updating token field of token credential

// Use AnonymousCredential when url already includes a SAS signature
Expand Down
44 changes: 44 additions & 0 deletions sdk/storage/storage-blob/samples/typescript/azureAdAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Setup: Enter your Azure Active Directory credentials as described in main()
*/

import { BlobServiceClient, SharedKeyCredential } from "../../src"; // Change to "@azure/storage-blob" in your package
import { DefaultAzureCredential } from "@azure/identity";

async function main() {
// Enter your storage account name
const account = "";

// DefaultAzureCredential will first look for Azure Active Directory (AAD)
// client secret credentials in the following environment variables:
//
// - AZURE_TENANT_ID: The ID of your AAD tenant
// - AZURE_CLIENT_ID: The ID of your AAD app registration (client)
// - AZURE_CLIENT_SECRET: The client secret for your AAD app registration
//
// If those environment variables aren't found and your application is deployed
// to an Azure VM or App Service instance, the managed service identity endpoint
// will be used as a fallback authentication source.
const defaultAzureCredential = new DefaultAzureCredential();

const blobServiceClient = new BlobServiceClient(
`https://${account}.blob.core.windows.net`,
defaultAzureCredential,
);

// Create a container
const containerName = `newcontainer${new Date().getTime()}`;
const createContainerResponse = await blobServiceClient
.createContainerClient(containerName)
.create();
console.log(`Created container ${containerName} successfully`, createContainerResponse.requestId);
}

// An async method returns a Promise object, which is compatible with then().catch() coding style.
main()
.then(() => {
console.log("Successfully executed the sample.");
})
.catch((err) => {
console.log(err.message);
});
9 changes: 6 additions & 3 deletions sdk/storage/storage-blob/samples/typescript/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Models,
SharedKeyCredential,
newPipeline,
TokenCredential,
RawTokenCredential,
} from "../../src"; // Change to "@azure/storage-blob" in your package

async function main() {
Expand All @@ -18,8 +18,11 @@ async function main() {
// Use SharedKeyCredential with storage account and account key
const sharedKeyCredential = new SharedKeyCredential(account, accountKey);

// Use TokenCredential with OAuth token
const tokenCredential = new TokenCredential("token");
// Use RawTokenCredential with OAuth token. You can find more
// TokenCredential implementations in the @azure/identity library
// to use client secrets, certificates, or managed identities for
// authentication.
const tokenCredential = new RawTokenCredential("token");
tokenCredential.token = "renewedToken"; // Renew the token by updating token field of token credential

// Use AnonymousCredential when url already includes a SAS signature
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/storage-blob/src/Aborter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AbortSignalLike, isNode } from "@azure/ms-rest-js";
import { AbortSignalLike, isNode } from "@azure/core-http";

/**
* An aborter instance implements AbortSignal interface, can abort HTTP requests.
Expand Down
21 changes: 15 additions & 6 deletions sdk/storage/storage-blob/src/AppendBlobClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { HttpRequestBody, TransferProgressEvent } from "@azure/ms-rest-js";
import {
HttpRequestBody,
TransferProgressEvent,
TokenCredential,
isTokenCredential
} from "@azure/core-http";

import * as Models from "./generated/lib/models";
import { Aborter } from "./Aborter";
Expand Down Expand Up @@ -143,12 +148,13 @@ export class AppendBlobClient extends BlobClient {
* Encoded URL string will NOT be escaped twice, only special characters in URL path will be escaped.
* However, if a blob name includes ? or %, blob name must be encoded in the URL.
* Such as a blob named "my?blob%", the URL should be "https://myaccount.blob.core.windows.net/mycontainer/my%3Fblob%25".
* @param {Credential} credential Such as AnonymousCredential, SharedKeyCredential or TokenCredential.
* If not specified, AnonymousCredential is used.
* @param {Credential | TokenCredential} credential Such as AnonymousCredential, SharedKeyCredential, RawTokenCredential,
* or a TokenCredential from @azure/identity. If not specified,
* AnonymousCredential is used.
* @param {NewPipelineOptions} [options] Optional. Options to configure the HTTP pipeline.
* @memberof AppendBlobClient
*/
constructor(url: string, credential: Credential, options?: NewPipelineOptions);
constructor(url: string, credential: Credential | TokenCredential, options?: NewPipelineOptions);
/**
* Creates an instance of AppendBlobClient.
* This method accepts an encoded URL or non-encoded URL pointing to an append blob.
Expand All @@ -170,7 +176,7 @@ export class AppendBlobClient extends BlobClient {
constructor(url: string, pipeline: Pipeline);
constructor(
urlOrConnectionString: string,
credentialOrPipelineOrContainerName: string | Credential | Pipeline,
credentialOrPipelineOrContainerName: string | Credential | TokenCredential | Pipeline,
blobNameOrOptions?: string | NewPipelineOptions,
options?: NewPipelineOptions
) {
Expand All @@ -179,7 +185,10 @@ export class AppendBlobClient extends BlobClient {
let pipeline: Pipeline;
if (credentialOrPipelineOrContainerName instanceof Pipeline) {
pipeline = credentialOrPipelineOrContainerName;
} else if (credentialOrPipelineOrContainerName instanceof Credential) {
} else if (
credentialOrPipelineOrContainerName instanceof Credential ||
isTokenCredential(credentialOrPipelineOrContainerName)
) {
options = blobNameOrOptions as NewPipelineOptions;
pipeline = newPipeline(credentialOrPipelineOrContainerName, options);
} else if (
Expand Down
21 changes: 15 additions & 6 deletions sdk/storage/storage-blob/src/BlobClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { isNode, TransferProgressEvent } from "@azure/ms-rest-js";
import {
isNode,
TransferProgressEvent,
TokenCredential,
isTokenCredential
} from "@azure/core-http";

import * as Models from "./generated/lib/models";
import { Aborter } from "./Aborter";
Expand Down Expand Up @@ -579,12 +584,13 @@ export class BlobClient extends StorageClient {
* @param {string} url A Client string pointing to Azure Storage blob service, such as
* "https://myaccount.blob.core.windows.net". You can append a SAS
* if using AnonymousCredential, such as "https://myaccount.blob.core.windows.net?sasString".
* @param {Credential} credential Such as AnonymousCredential, SharedKeyCredential or TokenCredential.
* If not specified, AnonymousCredential is used.
* @param {Credential | TokenCredential} credential Such as AnonymousCredential, SharedKeyCredential, RawTokenCredential,
* or a TokenCredential from @azure/identity. If not specified,
* AnonymousCredential is used.
* @param {NewPipelineOptions} [options] Optional. Options to configure the HTTP pipeline.
* @memberof BlobClient
*/
constructor(url: string, credential?: Credential, options?: NewPipelineOptions);
constructor(url: string, credential?: Credential | TokenCredential, options?: NewPipelineOptions);
/**
* Creates an instance of BlobClient.
* This method accepts an encoded URL or non-encoded URL pointing to a blob.
Expand All @@ -606,14 +612,17 @@ export class BlobClient extends StorageClient {
constructor(url: string, pipeline: Pipeline);
constructor(
urlOrConnectionString: string,
credentialOrPipelineOrContainerName?: string | Credential | Pipeline,
credentialOrPipelineOrContainerName?: string | Credential | TokenCredential | Pipeline,
blobNameOrOptions?: string | NewPipelineOptions,
options?: NewPipelineOptions
) {
let pipeline: Pipeline;
if (credentialOrPipelineOrContainerName instanceof Pipeline) {
pipeline = credentialOrPipelineOrContainerName;
} else if (credentialOrPipelineOrContainerName instanceof Credential) {
} else if (
credentialOrPipelineOrContainerName instanceof Credential ||
isTokenCredential(credentialOrPipelineOrContainerName)
) {
options = blobNameOrOptions as NewPipelineOptions;
pipeline = newPipeline(credentialOrPipelineOrContainerName, options);
} else if (
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/storage-blob/src/BlobDownloadResponse.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { HttpResponse, isNode } from "@azure/ms-rest-js";
import { HttpResponse, isNode } from "@azure/core-http";

import * as Models from "./generated/lib/models";
import { Metadata } from "./models";
Expand Down
Loading

0 comments on commit b2ac1b7

Please sign in to comment.