Implemented ServicePrincipal login with certificate (#53)
* update nyc dependency to fix security warning from npm * Implemented ServicePrincipal login with certificate * code review feedback * Add samples that can be run live
This commit is contained in:
Родитель
0855f90c6c
Коммит
291bbb9d48
|
@ -62,5 +62,10 @@ typings/
|
|||
*.js
|
||||
*.js.map
|
||||
|
||||
# package-lock.json
|
||||
package-lock.json
|
||||
# package-lock.json
|
||||
package-lock.json
|
||||
|
||||
# additional metadata
|
||||
authfileWithCert.json
|
||||
authfileWithSecret.json
|
||||
spcert.pem
|
32
Changelog.md
32
Changelog.md
|
@ -1,8 +1,38 @@
|
|||
# Changelog
|
||||
|
||||
## 1.0.0 - 2019/05/06
|
||||
- Added support for ServicePrincipal login with certificates.
|
||||
- Updated dependencies to their latest versions.
|
||||
|
||||
## 0.9.3 - 2019/04/04
|
||||
- Updated `@azure/ms-rest-js` to the latest version `^1.8.1`.
|
||||
|
||||
## 0.9.2 - 2019/03/26
|
||||
- Updated the return types for calls using interactive login, user name/ password and service principal to return the right types with promise flavor methods.
|
||||
|
||||
## 0.9.1 - 2019/01/15
|
||||
- Fixed issues in AppService MSI login.
|
||||
- Improved documentation of `MSIAppServiceTokenCredentials.getToken()`
|
||||
## 0.9.0 - 2019/01/11
|
||||
- Added support for custom MSI endpoint.
|
||||
|
||||
## 0.8.4 - 2019/01/09
|
||||
- Exported MSI login methods from the package.
|
||||
|
||||
## 0.8.3 - 2018/12/18
|
||||
- Added a check for verifying the package.json version
|
||||
- Added azure pipelines for CI.
|
||||
|
||||
## 0.8.2 - 2018/11/19
|
||||
- Fixed incorrect path in the "main" node of package.json.
|
||||
|
||||
## 0.8.1 - 2018/11/19
|
||||
- Added owners and issue template.
|
||||
- Improved internal structure of the package.
|
||||
|
||||
## 0.8.0 - 2018/11/12
|
||||
|
||||
- Rename package to "@azure/ms-rest-nodeauth"
|
||||
- Renamed package to "@azure/ms-rest-nodeauth"
|
||||
|
||||
## 0.6.0 - 2018/09/27
|
||||
|
||||
|
|
54
README.md
54
README.md
|
@ -19,7 +19,7 @@ msRestNodeAuth.loginWithUsernamePasswordWithAuthResponse(username, password).the
|
|||
});
|
||||
```
|
||||
|
||||
### service-principal/secret based login
|
||||
### service-principal and secret based login
|
||||
```typescript
|
||||
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
|
||||
|
||||
|
@ -34,6 +34,45 @@ msRestNodeAuth.loginWithServicePrincipalSecretWithAuthResponse(clientId, secret,
|
|||
});
|
||||
```
|
||||
|
||||
#### service-principal and certificate based login by providing an ABSOLUTE file path to the .pem file
|
||||
```typescript
|
||||
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
|
||||
|
||||
const clientId = process.env["CLIENT_ID"];
|
||||
const tenantId = process.env["DOMAIN"];
|
||||
|
||||
msRestNodeAuth.loginWithServicePrincipalCertificateWithAuthResponse(clientId, "/Users/user1/foo.pem", tenantId).then((authres) => {
|
||||
console.dir(authres, { depth: null })
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
```
|
||||
|
||||
#### service-principal and certificate based login by providing the certificate and private key (contents of the .pem file)
|
||||
```typescript
|
||||
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
|
||||
|
||||
const clientId = process.env["CLIENT_ID"];
|
||||
const tenantId = process.env["DOMAIN"];
|
||||
const certificate =
|
||||
`
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
xxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxx
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
yyyyyyyyyyyyyyyyy
|
||||
yyyyyyyyyyyyyyyyy
|
||||
-----END CERTIFICATE-----
|
||||
`;
|
||||
|
||||
msRestNodeAuth.loginWithServicePrincipalCertificateWithAuthResponse(clientId, certificate, tenantId).then((authres) => {
|
||||
console.dir(authres, { depth: null })
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
```
|
||||
|
||||
### interactive/device-code flow login
|
||||
```typescript
|
||||
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
|
||||
|
@ -46,6 +85,19 @@ msRestNodeAuth.interactiveLoginWithAuthResponse().then((authres) => {
|
|||
```
|
||||
|
||||
### service-principal authentication from auth file on disk
|
||||
Before using this method please install az cli from https://github.com/Azure/azure-cli/releases.
|
||||
Then execute `az ad sp create-for-rbac --sdk-auth > ${yourFilename.json}`.
|
||||
|
||||
If you want to create the sp for a different cloud/environment then please execute:
|
||||
1. az cloud list
|
||||
2. az cloud set –n <name of the environment>
|
||||
3. az ad sp create-for-rbac --sdk-auth > auth.json // create sp with **secret**.
|
||||
**OR**
|
||||
az ad sp create-for-rbac --create-cert --sdk-auth > auth.json // create sp with **certificate**.
|
||||
If the service principal is already created then login with service principal info:
|
||||
4. az login --service-principal -u <clientId> -p <clientSecret> -t <tenantId>
|
||||
5. az account show --sdk-auth > auth.json
|
||||
|
||||
```typescript
|
||||
import * as msRestNodeAuth from "../lib/msRestNodeAuth";
|
||||
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { createHash } from "crypto";
|
||||
import { ApplicationTokenCredentialsBase } from "./applicationTokenCredentialsBase";
|
||||
import { Environment } from "@azure/ms-rest-azure-env";
|
||||
import { AuthConstants, TokenAudience } from "../util/authConstants";
|
||||
import { TokenResponse, ErrorResponse, TokenCache } from "adal-node";
|
||||
import { AzureTokenCredentialsOptions } from "../login";
|
||||
|
||||
export class ApplicationTokenCertificateCredentials extends ApplicationTokenCredentialsBase {
|
||||
readonly certificate: string;
|
||||
readonly thumbprint: string;
|
||||
|
||||
/**
|
||||
* Creates a new ApplicationTokenCredentials object.
|
||||
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
|
||||
* for detailed instructions on creating an Azure Active Directory application.
|
||||
* @constructor
|
||||
* @param {string} clientId The active directory application client id.
|
||||
* @param {string} domain The domain or tenant id containing this application.
|
||||
* @param {string} certificate A PEM encoded certificate private key.
|
||||
* @param {string} thumbprint A hex encoded thumbprint of the certificate.
|
||||
* @param {string} [tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch', or any other resource like 'https://vault.azure.com/'.
|
||||
* If tokenAudience is 'graph' then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
|
||||
* @param {Environment} [environment] The azure environment to authenticate with.
|
||||
* @param {object} [tokenCache] The token cache. Default value is the MemoryCache object from adal.
|
||||
*/
|
||||
public constructor(
|
||||
clientId: string,
|
||||
domain: string,
|
||||
certificate: string,
|
||||
thumbprint: string,
|
||||
tokenAudience?: TokenAudience,
|
||||
environment?: Environment,
|
||||
tokenCache?: TokenCache
|
||||
) {
|
||||
if (!certificate || typeof certificate.valueOf() !== "string") {
|
||||
throw new Error("certificate must be a non empty string.");
|
||||
}
|
||||
if (!thumbprint || typeof thumbprint.valueOf() !== "string") {
|
||||
throw new Error("thumbprint must be a non empty string.");
|
||||
}
|
||||
super(clientId, domain, tokenAudience, environment, tokenCache);
|
||||
|
||||
this.certificate = certificate;
|
||||
this.thumbprint = thumbprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the token from cache initially. If that is unsuccessfull then it tries to get the token from ADAL.
|
||||
* @returns {Promise<TokenResponse>} A promise that resolves to TokenResponse and rejects with an Error.
|
||||
*/
|
||||
public async getToken(): Promise<TokenResponse> {
|
||||
try {
|
||||
const tokenResponse = await this.getTokenFromCache();
|
||||
return tokenResponse;
|
||||
} catch (error) {
|
||||
if (error.message.startsWith(AuthConstants.SDK_INTERNAL_ERROR)) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const resource = this.getActiveDirectoryResourceId();
|
||||
this.authContext.acquireTokenWithClientCertificate(
|
||||
resource,
|
||||
this.clientId,
|
||||
this.certificate,
|
||||
this.thumbprint,
|
||||
(error: any, tokenResponse: TokenResponse | ErrorResponse) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
if (tokenResponse.error || tokenResponse.errorDescription) {
|
||||
return reject(tokenResponse);
|
||||
}
|
||||
return resolve(tokenResponse as TokenResponse);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of ApplicationTokenCertificateCredentials.
|
||||
*
|
||||
* @param clientId The active directory application client id also known as the SPN (ServicePrincipal Name).
|
||||
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
|
||||
* for an example.
|
||||
* @param {string} certificateStringOrFilePath A PEM encoded certificate and private key OR an absolute filepath to the .pem file containing that information. For example:
|
||||
* - CertificateString: "-----BEGIN PRIVATE KEY-----\n<xxxxx>\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n<yyyyy>\n-----END CERTIFICATE-----\n"
|
||||
* - CertificateFilePath: **Absolute** file path of the .pem file.
|
||||
* @param domain The domain or tenant id containing this application.
|
||||
* @param options AzureTokenCredentialsOptions - Object representing optional parameters.
|
||||
*
|
||||
* @returns ApplicationTokenCertificateCredentials
|
||||
*/
|
||||
public static create(
|
||||
clientId: string,
|
||||
certificateStringOrFilePath: string,
|
||||
domain: string,
|
||||
options: AzureTokenCredentialsOptions
|
||||
): ApplicationTokenCertificateCredentials {
|
||||
if (
|
||||
!certificateStringOrFilePath ||
|
||||
typeof certificateStringOrFilePath.valueOf() !== "string"
|
||||
) {
|
||||
throw new Error(
|
||||
"'certificateStringOrFilePath' must be a non empty string."
|
||||
);
|
||||
}
|
||||
if (!certificateStringOrFilePath.startsWith("-----BEGIN")) {
|
||||
certificateStringOrFilePath = readFileSync(
|
||||
certificateStringOrFilePath,
|
||||
"utf8"
|
||||
);
|
||||
}
|
||||
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9\+\/\n\r]+\=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/;
|
||||
const matchCert = certificateStringOrFilePath.match(certificatePattern);
|
||||
const rawCertificate = matchCert ? matchCert[3] : "";
|
||||
if (!rawCertificate) {
|
||||
throw new Error(
|
||||
"Unable to correctly parse the certificate from the value provided in 'certificateStringOrFilePath' "
|
||||
);
|
||||
}
|
||||
const thumbprint = createHash("sha1")
|
||||
.update(Buffer.from(rawCertificate, "base64"))
|
||||
.digest("hex");
|
||||
return new ApplicationTokenCertificateCredentials(
|
||||
clientId,
|
||||
domain,
|
||||
certificateStringOrFilePath,
|
||||
thumbprint,
|
||||
options.tokenAudience,
|
||||
options.environment,
|
||||
options.tokenCache
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { TokenCredentialsBase } from "./tokenCredentialsBase";
|
||||
import { ApplicationTokenCredentialsBase } from "./applicationTokenCredentialsBase";
|
||||
import { Environment } from "@azure/ms-rest-azure-env";
|
||||
import { AuthConstants, TokenAudience } from "../util/authConstants";
|
||||
import { TokenResponse, ErrorResponse } from "adal-node";
|
||||
|
||||
export class ApplicationTokenCredentials extends TokenCredentialsBase {
|
||||
import { TokenResponse, ErrorResponse, TokenCache } from "adal-node";
|
||||
|
||||
export class ApplicationTokenCredentials extends ApplicationTokenCredentialsBase {
|
||||
readonly secret: string;
|
||||
|
||||
/**
|
||||
|
@ -29,12 +28,12 @@ export class ApplicationTokenCredentials extends TokenCredentialsBase {
|
|||
secret: string,
|
||||
tokenAudience?: TokenAudience,
|
||||
environment?: Environment,
|
||||
tokenCache?: any) {
|
||||
|
||||
if (!Boolean(secret) || typeof secret.valueOf() !== "string") {
|
||||
tokenCache?: TokenCache
|
||||
) {
|
||||
if (!secret || typeof secret.valueOf() !== "string") {
|
||||
throw new Error("secret must be a non empty string.");
|
||||
}
|
||||
super(clientId, domain, tokenAudience, environment as any, tokenCache);
|
||||
super(clientId, domain, tokenAudience, environment, tokenCache);
|
||||
|
||||
this.secret = secret;
|
||||
}
|
||||
|
@ -43,84 +42,34 @@ export class ApplicationTokenCredentials extends TokenCredentialsBase {
|
|||
* Tries to get the token from cache initially. If that is unsuccessfull then it tries to get the token from ADAL.
|
||||
* @returns {Promise<TokenResponse>} A promise that resolves to TokenResponse and rejects with an Error.
|
||||
*/
|
||||
public getToken(): Promise<TokenResponse> {
|
||||
return this.getTokenFromCache()
|
||||
.then((tokenResponse) => tokenResponse)
|
||||
.catch((error) => {
|
||||
if (error.message.startsWith(AuthConstants.SDK_INTERNAL_ERROR)) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
const resource = this.getActiveDirectoryResourceId();
|
||||
return new Promise((resolve, reject) => {
|
||||
this.authContext.acquireTokenWithClientCredentials(resource, this.clientId, this.secret,
|
||||
(error: any, tokenResponse: TokenResponse | ErrorResponse) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
if (tokenResponse.error || tokenResponse.errorDescription) {
|
||||
return reject(tokenResponse);
|
||||
}
|
||||
|
||||
return resolve(tokenResponse);
|
||||
});
|
||||
});
|
||||
public async getToken(): Promise<TokenResponse> {
|
||||
try {
|
||||
const tokenResponse = await this.getTokenFromCache();
|
||||
return tokenResponse;
|
||||
} catch (error) {
|
||||
if (
|
||||
error.message &&
|
||||
error.message.startsWith(AuthConstants.SDK_INTERNAL_ERROR)
|
||||
) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
const resource = this.getActiveDirectoryResourceId();
|
||||
return new Promise((resolve, reject) => {
|
||||
this.authContext.acquireTokenWithClientCredentials(
|
||||
resource,
|
||||
this.clientId,
|
||||
this.secret,
|
||||
(error: any, tokenResponse: TokenResponse | ErrorResponse) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
if (tokenResponse.error || tokenResponse.errorDescription) {
|
||||
return reject(tokenResponse);
|
||||
}
|
||||
return resolve(tokenResponse as TokenResponse);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected getTokenFromCache(): Promise<any> {
|
||||
const self = this;
|
||||
|
||||
// a thin wrapper over the base implementation. try get token from cache, additionaly clean up cache if required.
|
||||
return super.getTokenFromCache(undefined).then((tokenResponse: TokenResponse) => {
|
||||
return Promise.resolve(tokenResponse);
|
||||
}).catch((error: any) => {
|
||||
// Remove the stale token from the tokencache. ADAL gives the same error message "Entry not found in cache."
|
||||
// for entry not being present in the cache and for accessToken being expired in the cache. We do not want the token cache
|
||||
// to contain the expired token, we clean it up here.
|
||||
return self.removeInvalidItemsFromCache({ _clientId: self.clientId }).then((status) => {
|
||||
if (status.result) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
const msg = status && status.details && status.details.message ? status.details.message : status.details;
|
||||
return Promise.reject(new Error(AuthConstants.SDK_INTERNAL_ERROR + " : "
|
||||
+ "critical failure while removing expired token for service principal from token cache. "
|
||||
+ msg));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes invalid items from token cache. This method is different. Here we never reject in case of error.
|
||||
* Rather we resolve with an object that says the result is false and error information is provided in
|
||||
* the details property of the resolved object. This is done to do better error handling in the above function
|
||||
* where removeInvalidItemsFromCache() is called.
|
||||
* @param {object} query The query to be used for finding the token for service principal from the cache
|
||||
* @returns {result: boolean, details?: Error} resultObject with more info.
|
||||
*/
|
||||
private removeInvalidItemsFromCache(query: object): Promise<{ result: boolean, details?: Error }> {
|
||||
const self = this;
|
||||
return new Promise<{ result: boolean, details?: Error }>((resolve) => {
|
||||
self.tokenCache.find(query, (error: Error, entries: any[]) => {
|
||||
if (error) {
|
||||
return resolve({ result: false, details: error });
|
||||
}
|
||||
|
||||
if (entries && entries.length > 0) {
|
||||
// return resolve(self.tokenCache.remove(entries, () => resolve({ result: true })));
|
||||
return new Promise((resolve) => {
|
||||
return self.tokenCache.remove(entries, (err: Error) => {
|
||||
if (err) {
|
||||
return resolve({ result: false, details: err });
|
||||
}
|
||||
return resolve({ result: true });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return resolve({ result: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { TokenCredentialsBase } from "./tokenCredentialsBase";
|
||||
import { Environment } from "@azure/ms-rest-azure-env";
|
||||
import { AuthConstants, TokenAudience } from "../util/authConstants";
|
||||
import { TokenCache, TokenResponse } from "adal-node";
|
||||
|
||||
export abstract class ApplicationTokenCredentialsBase extends TokenCredentialsBase {
|
||||
/**
|
||||
* Creates a new ApplicationTokenCredentials object.
|
||||
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
|
||||
* for detailed instructions on creating an Azure Active Directory application.
|
||||
* @constructor
|
||||
* @param {string} clientId The active directory application client id.
|
||||
* @param {string} domain The domain or tenant id containing this application.
|
||||
* @param {string} [tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch', or any other resource like 'https://vault.azure.com/'.
|
||||
* If tokenAudience is 'graph' then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
|
||||
* @param {Environment} [environment] The azure environment to authenticate with.
|
||||
* @param {object} [tokenCache] The token cache. Default value is the MemoryCache object from adal.
|
||||
*/
|
||||
public constructor(
|
||||
clientId: string,
|
||||
domain: string,
|
||||
tokenAudience?: TokenAudience,
|
||||
environment?: Environment,
|
||||
tokenCache?: TokenCache
|
||||
) {
|
||||
super(clientId, domain, tokenAudience, environment, tokenCache);
|
||||
}
|
||||
|
||||
protected async getTokenFromCache(): Promise<TokenResponse> {
|
||||
const self = this;
|
||||
|
||||
// a thin wrapper over the base implementation. try get token from cache, additionaly clean up cache if required.
|
||||
try {
|
||||
const tokenResponse = await super.getTokenFromCache(undefined);
|
||||
return Promise.resolve(tokenResponse);
|
||||
} catch (error) {
|
||||
// Remove the stale token from the tokencache. ADAL gives the same error message "Entry not found in cache."
|
||||
// for entry not being present in the cache and for accessToken being expired in the cache. We do not want the token cache
|
||||
// to contain the expired token, we clean it up here.
|
||||
const status = await self.removeInvalidItemsFromCache({
|
||||
_clientId: self.clientId
|
||||
});
|
||||
if (status.result) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
const message =
|
||||
status && status.details && status.details.message
|
||||
? status.details.message
|
||||
: status.details;
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
AuthConstants.SDK_INTERNAL_ERROR +
|
||||
" : " +
|
||||
"critical failure while removing expired token for service principal from token cache. " +
|
||||
message
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes invalid items from token cache. This method is different. Here we never reject in case of error.
|
||||
* Rather we resolve with an object that says the result is false and error information is provided in
|
||||
* the details property of the resolved object. This is done to do better error handling in the above function
|
||||
* where removeInvalidItemsFromCache() is called.
|
||||
* @param {object} query The query to be used for finding the token for service principal from the cache
|
||||
* @returns {result: boolean, details?: Error} resultObject with more info.
|
||||
*/
|
||||
private removeInvalidItemsFromCache(
|
||||
query: object
|
||||
): Promise<{ result: boolean; details?: Error }> {
|
||||
const self = this;
|
||||
return new Promise<{ result: boolean; details?: Error }>(resolve => {
|
||||
self.tokenCache.find(query, (error: Error, entries: any[]) => {
|
||||
if (error) {
|
||||
return resolve({ result: false, details: error });
|
||||
}
|
||||
|
||||
if (entries && entries.length > 0) {
|
||||
return new Promise(resolve => {
|
||||
return self.tokenCache.remove(entries, (err: Error) => {
|
||||
if (err) {
|
||||
return resolve({ result: false, details: err });
|
||||
}
|
||||
return resolve({ result: true });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return resolve({ result: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
import { TokenCredentialsBase } from "./tokenCredentialsBase";
|
||||
import { Environment } from "@azure/ms-rest-azure-env";
|
||||
import { AuthConstants, TokenAudience } from "../util/authConstants";
|
||||
import { TokenResponse } from "adal-node";
|
||||
import { TokenResponse, TokenCache } from "adal-node";
|
||||
|
||||
export class DeviceTokenCredentials extends TokenCredentialsBase {
|
||||
|
||||
|
@ -34,7 +34,7 @@ export class DeviceTokenCredentials extends TokenCredentialsBase {
|
|||
username?: string,
|
||||
tokenAudience?: TokenAudience,
|
||||
environment?: Environment,
|
||||
tokenCache?: any) {
|
||||
tokenCache?: TokenCache) {
|
||||
|
||||
if (!username) {
|
||||
username = "user@example.com";
|
||||
|
@ -48,7 +48,7 @@ export class DeviceTokenCredentials extends TokenCredentialsBase {
|
|||
clientId = AuthConstants.DEFAULT_ADAL_CLIENT_ID;
|
||||
}
|
||||
|
||||
super(clientId, domain, tokenAudience, environment as any, tokenCache);
|
||||
super(clientId, domain, tokenAudience, environment, tokenCache);
|
||||
|
||||
this.username = username;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { ApplicationTokenCredentials } from "./applicationTokenCredentials";
|
||||
import { ApplicationTokenCertificateCredentials } from "./applicationTokenCertificateCredentials";
|
||||
import { DeviceTokenCredentials } from "./deviceTokenCredentials";
|
||||
import { MSIAppServiceTokenCredentials } from "./msiAppServiceTokenCredentials";
|
||||
import { MSITokenCredentials } from "./msiTokenCredentials";
|
||||
|
@ -62,6 +63,9 @@ function _createAuthenticatorMapper(credentials: MSITokenCredentials): Authentic
|
|||
if (credentials instanceof ApplicationTokenCredentials) {
|
||||
return context.acquireTokenWithClientCredentials(
|
||||
challenge.resource, credentials.clientId, credentials.secret, _formAuthorizationValue);
|
||||
} else if (credentials instanceof ApplicationTokenCertificateCredentials) {
|
||||
return context.acquireTokenWithClientCertificate(
|
||||
challenge.resource, credentials.clientId, credentials.certificate, credentials.thumbprint, _formAuthorizationValue);
|
||||
} else if (credentials instanceof UserTokenCredentials) {
|
||||
return context.acquireTokenWithUsernamePassword(
|
||||
challenge.resource, credentials.username, credentials.password, credentials.clientId, _formAuthorizationValue);
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Constants as MSRestConstants, WebResource } from "@azure/ms-rest-js";
|
|||
import { Environment } from "@azure/ms-rest-azure-env";
|
||||
import { TokenAudience } from "../util/authConstants";
|
||||
import { TokenClientCredentials } from "./tokenClientCredentials";
|
||||
import { TokenResponse, AuthenticationContext, MemoryCache, ErrorResponse } from "adal-node";
|
||||
import { TokenResponse, AuthenticationContext, MemoryCache, ErrorResponse, TokenCache } from "adal-node";
|
||||
|
||||
export abstract class TokenCredentialsBase implements TokenClientCredentials {
|
||||
public readonly authContext: AuthenticationContext;
|
||||
|
@ -14,14 +14,14 @@ export abstract class TokenCredentialsBase implements TokenClientCredentials {
|
|||
public readonly clientId: string,
|
||||
public domain: string,
|
||||
public readonly tokenAudience?: TokenAudience,
|
||||
public readonly environment = Environment.AzureCloud,
|
||||
public tokenCache: any = new MemoryCache()) {
|
||||
public readonly environment: Environment = Environment.AzureCloud,
|
||||
public tokenCache: TokenCache = new MemoryCache()) {
|
||||
|
||||
if (!Boolean(clientId) || typeof clientId.valueOf() !== "string") {
|
||||
if (!clientId || typeof clientId.valueOf() !== "string") {
|
||||
throw new Error("clientId must be a non empty string.");
|
||||
}
|
||||
|
||||
if (!Boolean(domain) || typeof domain.valueOf() !== "string") {
|
||||
if (!domain || typeof domain.valueOf() !== "string") {
|
||||
throw new Error("domain must be a non empty string.");
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,9 @@ export abstract class TokenCredentialsBase implements TokenClientCredentials {
|
|||
if (this.tokenAudience) {
|
||||
resource = this.tokenAudience;
|
||||
if (this.tokenAudience.toLowerCase() === "graph") {
|
||||
resource = this.environment.activeDirectoryGraphResourceId;
|
||||
resource = this.environment.activeDirectoryGraphResourceId as string;
|
||||
} else if (this.tokenAudience.toLowerCase() === "batch") {
|
||||
resource = this.environment.batchResourceId;
|
||||
resource = this.environment.batchResourceId as string;
|
||||
}
|
||||
}
|
||||
return resource;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { TokenCredentialsBase } from "./tokenCredentialsBase";
|
||||
import { Environment } from "@azure/ms-rest-azure-env";
|
||||
import { TokenAudience } from "../util/authConstants";
|
||||
import { TokenResponse, ErrorResponse } from "adal-node";
|
||||
import { TokenResponse, ErrorResponse, TokenCache } from "adal-node";
|
||||
|
||||
export class UserTokenCredentials extends TokenCredentialsBase {
|
||||
|
||||
|
@ -33,25 +33,25 @@ export class UserTokenCredentials extends TokenCredentialsBase {
|
|||
password: string,
|
||||
tokenAudience?: TokenAudience,
|
||||
environment?: Environment,
|
||||
tokenCache?: any) {
|
||||
tokenCache?: TokenCache) {
|
||||
|
||||
if (!Boolean(clientId) || typeof clientId.valueOf() !== "string") {
|
||||
if (!clientId || typeof clientId.valueOf() !== "string") {
|
||||
throw new Error("clientId must be a non empty string.");
|
||||
}
|
||||
|
||||
if (!Boolean(domain) || typeof domain.valueOf() !== "string") {
|
||||
if (!domain || typeof domain.valueOf() !== "string") {
|
||||
throw new Error("domain must be a non empty string.");
|
||||
}
|
||||
|
||||
if (!Boolean(username) || typeof username.valueOf() !== "string") {
|
||||
if (!username || typeof username.valueOf() !== "string") {
|
||||
throw new Error("username must be a non empty string.");
|
||||
}
|
||||
|
||||
if (!Boolean(password) || typeof password.valueOf() !== "string") {
|
||||
if (!password || typeof password.valueOf() !== "string") {
|
||||
throw new Error("password must be a non empty string.");
|
||||
}
|
||||
|
||||
super(clientId, domain, tokenAudience, environment as any, tokenCache);
|
||||
super(clientId, domain, tokenAudience, environment, tokenCache);
|
||||
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
|
|
148
lib/login.ts
148
lib/login.ts
|
@ -2,11 +2,12 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as adal from "adal-node";
|
||||
import * as fs from "fs";
|
||||
import * as msRest from "@azure/ms-rest-js";
|
||||
import { readFileSync } from "fs";
|
||||
import { Environment } from "@azure/ms-rest-azure-env";
|
||||
import { TokenCredentialsBase } from "./credentials/tokenCredentialsBase";
|
||||
import { ApplicationTokenCredentials } from "./credentials/applicationTokenCredentials";
|
||||
import { ApplicationTokenCertificateCredentials } from "./credentials/applicationTokenCertificateCredentials";
|
||||
import { DeviceTokenCredentials } from "./credentials/deviceTokenCredentials";
|
||||
import { UserTokenCredentials } from "./credentials/userTokenCredentials";
|
||||
import { AuthConstants, TokenAudience } from "./util/authConstants";
|
||||
|
@ -48,9 +49,9 @@ export interface AzureTokenCredentialsOptions {
|
|||
*/
|
||||
environment?: Environment;
|
||||
/**
|
||||
* @property {any} [tokenCache] - The token cache. Default value is MemoryCache from adal.
|
||||
* @property {TokenCache} [tokenCache] - The token cache. Default value is MemoryCache from adal.
|
||||
*/
|
||||
tokenCache?: any;
|
||||
tokenCache?: adal.TokenCache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,7 +180,8 @@ export async function withUsernamePasswordWithAuthResponse(username: string, pas
|
|||
* @param {string} secret The application secret for the service principal.
|
||||
* @param {string} domain The domain or tenant id containing this application.
|
||||
* @param {object} [options] Object representing optional parameters.
|
||||
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid value is "graph".
|
||||
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch', or any other resource like 'https://vault.azure.com/'.
|
||||
* If tokenAudience is 'graph' then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
|
||||
* @param {Environment} [options.environment] The azure environment to authenticate with.
|
||||
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
|
||||
*
|
||||
|
@ -207,6 +209,46 @@ export async function withServicePrincipalSecretWithAuthResponse(clientId: strin
|
|||
return Promise.resolve({ credentials: creds, subscriptions: subscriptionList });
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an ApplicationTokenCertificateCredentials object and the list of subscriptions associated with that servicePrinicpalId/clientId across all the applicable tenants.
|
||||
*
|
||||
* @param {string} clientId The active directory application client id also known as the SPN (ServicePrincipal Name).
|
||||
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
|
||||
* for an example.
|
||||
* @param {string} certificateStringOrFilePath A PEM encoded certificate and private key OR an absolute filepath to the .pem file containing that information. For example:
|
||||
* - CertificateString: "-----BEGIN PRIVATE KEY-----\n<xxxxx>\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n<yyyyy>\n-----END CERTIFICATE-----\n"
|
||||
* - CertificateFilePath: **Absolute** file path of the .pem file.
|
||||
* @param {string} domain The domain or tenant id containing this application.
|
||||
* @param {object} [options] Object representing optional parameters.
|
||||
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch', or any other resource like 'https://vault.azure.com/'.
|
||||
* If tokenAudience is 'graph' then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
|
||||
* @param {Environment} [options.environment] The azure environment to authenticate with.
|
||||
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
|
||||
*
|
||||
* @returns {Promise<AuthResponse>} A Promise that resolves to AuthResponse that contains "credentials" and optional "subscriptions" array and rejects with an Error.
|
||||
*/
|
||||
export async function withServicePrincipalCertificateWithAuthResponse(clientId: string, certificateStringOrFilePath: string, domain: string, options?: AzureTokenCredentialsOptions): Promise<AuthResponse> {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (!options.environment) {
|
||||
options.environment = Environment.AzureCloud;
|
||||
}
|
||||
let creds: ApplicationTokenCertificateCredentials;
|
||||
let subscriptionList: LinkedSubscription[] = [];
|
||||
try {
|
||||
creds = ApplicationTokenCertificateCredentials.create(clientId, certificateStringOrFilePath, domain, options);
|
||||
await creds.getToken();
|
||||
// We only need to get the subscriptionList if the tokenAudience is for a management client.
|
||||
if (options.tokenAudience && options.tokenAudience === options.environment.activeDirectoryResourceId) {
|
||||
subscriptionList = await getSubscriptionsFromTenants(creds, [domain]);
|
||||
}
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
return Promise.resolve({ credentials: creds, subscriptions: subscriptionList });
|
||||
}
|
||||
|
||||
function validateAuthFileContent(credsObj: any, filePath: string) {
|
||||
if (!credsObj) {
|
||||
throw new Error("Please provide a credsObj to validate.");
|
||||
|
@ -217,8 +259,8 @@ function validateAuthFileContent(credsObj: any, filePath: string) {
|
|||
if (!credsObj.clientId) {
|
||||
throw new Error(`"clientId" is missing from the auth file: ${filePath}.`);
|
||||
}
|
||||
if (!credsObj.clientSecret) {
|
||||
throw new Error(`"clientSecret" is missing from the auth file: ${filePath}.`);
|
||||
if (!credsObj.clientSecret && !credsObj.clientCertificate) {
|
||||
throw new Error(`Either "clientSecret" or "clientCertificate" must be present in the auth file: ${filePath}.`);
|
||||
}
|
||||
if (!credsObj.subscriptionId) {
|
||||
throw new Error(`"subscriptionId" is missing from the auth file: ${filePath}.`);
|
||||
|
@ -259,11 +301,12 @@ function foundManagementEndpointUrl(authFileUrl: string, envUrl: string): boolea
|
|||
* If you want to create the sp for a different cloud/environment then please execute:
|
||||
* 1. az cloud list
|
||||
* 2. az cloud set –n <name of the environment>
|
||||
* 3. az ad sp create-for-rbac --sdk-auth > auth.json
|
||||
*
|
||||
* 3. az ad sp create-for-rbac --sdk-auth > auth.json // create sp with secret
|
||||
* **OR**
|
||||
* 3. az ad sp create-for-rbac --create-cert --sdk-auth > auth.json // create sp with certificate
|
||||
* If the service principal is already created then login with service principal info:
|
||||
* 3. az login --service-principal -u <clientId> -p <clientSecret> -t <tenantId>
|
||||
* 4. az account show --sdk-auth > auth.json
|
||||
* 4. az login --service-principal -u <clientId> -p <clientSecret> -t <tenantId>
|
||||
* 5. az account show --sdk-auth > auth.json
|
||||
*
|
||||
* Authenticates using the service principal information provided in the auth file. This method will set
|
||||
* the subscriptionId from the auth file to the user provided environment variable in the options
|
||||
|
@ -287,9 +330,9 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
|
|||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
let content: string, credsObj: any = {};
|
||||
const optionsForSpSecret: any = {};
|
||||
const optionsForSp: any = {};
|
||||
try {
|
||||
content = fs.readFileSync(filePath, { encoding: "utf8" });
|
||||
content = readFileSync(filePath, { encoding: "utf8" });
|
||||
credsObj = JSON.parse(content);
|
||||
validateAuthFileContent(credsObj, filePath);
|
||||
} catch (err) {
|
||||
|
@ -317,7 +360,7 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
|
|||
}
|
||||
}
|
||||
if (envFound.name) {
|
||||
optionsForSpSecret.environment = (Environment as any)[envFound.name];
|
||||
optionsForSp.environment = (Environment as any)[envFound.name];
|
||||
} else {
|
||||
// create a new environment with provided info.
|
||||
const envParams: any = {
|
||||
|
@ -327,7 +370,7 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
|
|||
const keys = Object.keys(credsObj);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (key.match(/^(clientId|clientSecret|subscriptionId|tenantId)$/ig) === null) {
|
||||
if (key.match(/^(clientId|clientSecret|clientCertificate|subscriptionId|tenantId)$/ig) === null) {
|
||||
if (key === "activeDirectoryEndpointUrl" && !key.endsWith("/")) {
|
||||
envParams[key] = credsObj[key] + "/";
|
||||
} else {
|
||||
|
@ -341,9 +384,13 @@ export async function withAuthFileWithAuthResponse(options?: LoginWithAuthFileOp
|
|||
if (!envParams.portalUrl) {
|
||||
envParams.portalUrl = "https://portal.azure.com";
|
||||
}
|
||||
optionsForSpSecret.environment = Environment.add(envParams);
|
||||
optionsForSp.environment = Environment.add(envParams);
|
||||
}
|
||||
return withServicePrincipalSecretWithAuthResponse(credsObj.clientId, credsObj.clientSecret, credsObj.tenantId, optionsForSpSecret);
|
||||
if (credsObj.clientSecret) {
|
||||
return withServicePrincipalSecretWithAuthResponse(credsObj.clientId, credsObj.clientSecret, credsObj.tenantId, optionsForSp);
|
||||
}
|
||||
|
||||
return withServicePrincipalCertificateWithAuthResponse(credsObj.clientId, credsObj.clientCertificate, credsObj.tenantId, optionsForSp);
|
||||
}
|
||||
|
||||
|
||||
|
@ -414,13 +461,13 @@ export async function withInteractiveWithAuthResponse(options?: InteractiveLogin
|
|||
interactiveOptions.language = options.language;
|
||||
interactiveOptions.userCodeResponseLogger = options.userCodeResponseLogger;
|
||||
const authorityUrl: string = interactiveOptions.environment.activeDirectoryEndpointUrl + interactiveOptions.domain;
|
||||
const authContext: any = new adal.AuthenticationContext(authorityUrl, interactiveOptions.environment.validateAuthority, interactiveOptions.tokenCache);
|
||||
const authContext = new adal.AuthenticationContext(authorityUrl, interactiveOptions.environment.validateAuthority, interactiveOptions.tokenCache);
|
||||
interactiveOptions.context = authContext;
|
||||
let userCodeResponse: any;
|
||||
let creds: DeviceTokenCredentials;
|
||||
|
||||
function tryAcquireToken(interactiveOptions: InteractiveLoginOptions, resolve: any, reject: any) {
|
||||
authContext.acquireUserCode(interactiveOptions.tokenAudience, interactiveOptions.clientId, interactiveOptions.language, (err: any, userCodeRes: any) => {
|
||||
authContext.acquireUserCode(interactiveOptions.tokenAudience!, interactiveOptions.clientId!, interactiveOptions.language!, (err: any, userCodeRes: adal.UserCodeInfo) => {
|
||||
if (err) {
|
||||
if (err.error === "authorization_pending") {
|
||||
setTimeout(() => {
|
||||
|
@ -482,11 +529,12 @@ export async function withInteractiveWithAuthResponse(options?: InteractiveLogin
|
|||
* If you want to create the sp for a different cloud/environment then please execute:
|
||||
* 1. az cloud list
|
||||
* 2. az cloud set –n <name of the environment>
|
||||
* 3. az ad sp create-for-rbac --sdk-auth > auth.json
|
||||
*
|
||||
* 3. az ad sp create-for-rbac --sdk-auth > auth.json // create sp with secret
|
||||
* **OR**
|
||||
* 3. az ad sp create-for-rbac --create-cert --sdk-auth > auth.json // create sp with certificate
|
||||
* If the service principal is already created then login with service principal info:
|
||||
* 3. az login --service-principal -u <clientId> -p <clientSecret> -t <tenantId>
|
||||
* 4. az account show --sdk-auth > auth.json
|
||||
* 4. az login --service-principal -u <clientId> -p <clientSecret> -t <tenantId>
|
||||
* 5. az account show --sdk-auth > auth.json
|
||||
*
|
||||
* Authenticates using the service principal information provided in the auth file. This method will set
|
||||
* the subscriptionId from the auth file to the user provided environment variable in the options
|
||||
|
@ -598,7 +646,8 @@ export function interactive(options?: InteractiveLoginOptions, callback?: { (err
|
|||
* @param {string} secret The application secret for the service principal.
|
||||
* @param {string} domain The domain or tenant id containing this application.
|
||||
* @param {object} [options] Object representing optional parameters.
|
||||
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid value is "graph".
|
||||
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch', or any other resource like 'https://vault.azure.com/'.
|
||||
* If tokenAudience is 'graph' then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
|
||||
* @param {Environment} [options.environment] The azure environment to authenticate with.
|
||||
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
|
||||
* @param {function} [optionalCallback] The optional callback.
|
||||
|
@ -639,6 +688,59 @@ export function withServicePrincipalSecret(clientId: string, secret: string, dom
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an ApplicationTokenCertificateCredentials object and the list of subscriptions associated with that servicePrinicpalId/clientId across all the applicable tenants.
|
||||
*
|
||||
* @param {string} clientId The active directory application client id also known as the SPN (ServicePrincipal Name).
|
||||
* See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
|
||||
* for an example.
|
||||
* @param {string} certificateStringOrFilePath A PEM encoded certificate and private key OR an absolute filepath to the .pem file containing that information. For example:
|
||||
* - CertificateString: "-----BEGIN PRIVATE KEY-----\n<xxxxx>\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\n<yyyyy>\n-----END CERTIFICATE-----\n"
|
||||
* - CertificateFilePath: **Absolute** file path of the .pem file.
|
||||
* @param {string} domain The domain or tenant id containing this application.
|
||||
* @param {object} [options] Object representing optional parameters.
|
||||
* @param {string} [options.tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch', or any other resource like 'https://vault.azure.com/'.
|
||||
* If tokenAudience is 'graph' then domain should also be provided and its value should not be the default 'common' tenant. It must be a string (preferrably in a guid format).
|
||||
* @param {Environment} [options.environment] The azure environment to authenticate with.
|
||||
* @param {object} [options.tokenCache] The token cache. Default value is the MemoryCache object from adal.
|
||||
* @param {function} [optionalCallback] The optional callback.
|
||||
*
|
||||
* @returns {function | Promise} If a callback was passed as the last parameter then it returns the callback else returns a Promise.
|
||||
*
|
||||
* {function} optionalCallback(err, credentials)
|
||||
* {Error} [err] - The Error object if an error occurred, null otherwise.
|
||||
* {ApplicationTokenCertificateCredentials} [credentials] - The ApplicationTokenCertificateCredentials object.
|
||||
* {Array} [subscriptions] - List of associated subscriptions across all the applicable tenants.
|
||||
* {Promise} A promise is returned.
|
||||
* @resolve {ApplicationTokenCertificateCredentials} The ApplicationTokenCertificateCredentials object.
|
||||
* @reject {Error} - The error object.
|
||||
*/
|
||||
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string): Promise<ApplicationTokenCertificateCredentials>;
|
||||
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, options: AzureTokenCredentialsOptions): Promise<ApplicationTokenCredentials>;
|
||||
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, options: AzureTokenCredentialsOptions, callback: { (err: Error, credentials: ApplicationTokenCertificateCredentials, subscriptions: Array<LinkedSubscription>): void }): void;
|
||||
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, callback: any): void;
|
||||
export function withServicePrincipalCertificate(clientId: string, certificateStringOrFilePath: string, domain: string, options?: AzureTokenCredentialsOptions, callback?: { (err: Error, credentials: ApplicationTokenCertificateCredentials, subscriptions: Array<LinkedSubscription>): void }): any {
|
||||
if (!callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = undefined;
|
||||
}
|
||||
const cb = callback as Function;
|
||||
if (!callback) {
|
||||
return withServicePrincipalCertificateWithAuthResponse(clientId, certificateStringOrFilePath, domain, options).then((authRes) => {
|
||||
return Promise.resolve(authRes.credentials);
|
||||
}).catch((err) => {
|
||||
return Promise.reject(err);
|
||||
});
|
||||
} else {
|
||||
msRest.promiseToCallback(withServicePrincipalCertificateWithAuthResponse(clientId, certificateStringOrFilePath, domain, options))((err: Error, authRes: AuthResponse) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
return cb(undefined, authRes.credentials, authRes.subscriptions);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a UserTokenCredentials object and the list of subscriptions associated with that userId across all the applicable tenants.
|
||||
* This method is applicable only for organizational ids that are not 2FA enabled otherwise please use interactive login.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export { ApplicationTokenCredentials } from "./credentials/applicationTokenCredentials";
|
||||
export { ApplicationTokenCertificateCredentials } from "./credentials/applicationTokenCertificateCredentials";
|
||||
export { DeviceTokenCredentials } from "./credentials/deviceTokenCredentials";
|
||||
export { createAuthenticator } from "./credentials/keyVaultFactory";
|
||||
export { MSIAppServiceOptions, MSIAppServiceTokenCredentials } from "./credentials/msiAppServiceTokenCredentials";
|
||||
|
@ -24,4 +25,6 @@ export {
|
|||
withAuthFileWithAuthResponse as loginWithAuthFileWithAuthResponse,
|
||||
loginWithVmMSI,
|
||||
loginWithAppServiceMSI,
|
||||
withServicePrincipalCertificate as loginWithServicePrincipalCertificate,
|
||||
withServicePrincipalCertificateWithAuthResponse as loginWithServicePrincipalCertificateWithAuthResponse
|
||||
} from "./login";
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import * as msRest from "@azure/ms-rest-js";
|
||||
import { TokenCredentialsBase } from "../credentials/tokenCredentialsBase";
|
||||
import { ApplicationTokenCredentials } from "../credentials/applicationTokenCredentials";
|
||||
import { ApplicationTokenCredentialsBase } from "../credentials/applicationTokenCredentialsBase";
|
||||
import { AuthConstants } from "../util/authConstants";
|
||||
|
||||
/**
|
||||
|
@ -107,7 +107,7 @@ export async function getSubscriptionsFromTenants(credentials: TokenCredentialsB
|
|||
let userType = "user";
|
||||
let username: string;
|
||||
const originalDomain = credentials.domain;
|
||||
if (credentials instanceof ApplicationTokenCredentials) {
|
||||
if (credentials instanceof ApplicationTokenCredentialsBase) {
|
||||
userType = "servicePrincipal";
|
||||
username = credentials.clientId;
|
||||
} else {
|
||||
|
|
23
package.json
23
package.json
|
@ -5,21 +5,10 @@
|
|||
"email": "azsdkteam@microsoft.com",
|
||||
"url": "https://github.com/Azure/ms-rest-nodeauth"
|
||||
},
|
||||
"version": "0.9.3",
|
||||
"version": "1.0.0",
|
||||
"description": "Azure Authentication library in node.js with type definitions.",
|
||||
"tags": [
|
||||
"node",
|
||||
"isomorphic",
|
||||
"browser",
|
||||
"azure",
|
||||
"autorest",
|
||||
"authentication",
|
||||
"environment"
|
||||
],
|
||||
"keywords": [
|
||||
"node",
|
||||
"isomorphic",
|
||||
"browser",
|
||||
"azure",
|
||||
"autorest",
|
||||
"authentication",
|
||||
|
@ -38,21 +27,23 @@
|
|||
"tsconfig.json"
|
||||
],
|
||||
"dependencies": {
|
||||
"@azure/ms-rest-azure-env": "^1.1.0",
|
||||
"@azure/ms-rest-js": "^1.8.1",
|
||||
"adal-node": "^0.1.22"
|
||||
"@azure/ms-rest-azure-env": "^1.1.2",
|
||||
"@azure/ms-rest-js": "^1.8.2",
|
||||
"adal-node": "^0.1.28"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@ts-common/azure-js-dev-tools": "^0.4.9",
|
||||
"@types/chai": "^4.1.6",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.12.0",
|
||||
"chai": "^4.2.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"mocha": "^5.2.0",
|
||||
"nock": "^10.0.1",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"nyc": "^13.1.0",
|
||||
"nyc": "^14.1.0",
|
||||
"rollup": "^0.67.1",
|
||||
"rollup-plugin-sourcemaps": "^0.4.2",
|
||||
"ts-node": "^7.0.1",
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# copy the content of this file to a file named ".env". It should be stored at the root of the repo.
|
||||
CLIENT_ID=
|
||||
DOMAIN=
|
||||
APPLICATION_SECRET=
|
||||
AZURE_USERNAME=
|
||||
AZURE_PASSWORD=
|
||||
# Absolute file paths.
|
||||
CERTIFICATE_FILE_PATH=
|
||||
AUTH_FILE_CERT_PATH=
|
||||
AUTH_FILE_SECRET_PATH=
|
|
@ -0,0 +1,19 @@
|
|||
import * as msRestNodeAuth from "../lib/msRestNodeAuth";
|
||||
import * as dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const authFilepath = process.env.AUTH_FILE_CERT_PATH || "";
|
||||
// copy the content of "sample.env" to a file named ".env". It should be stored at the root of the repo.
|
||||
// then from the root of the cloned repo run, ts-node ./samples/authFileWithSpCert.ts
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const authres = await msRestNodeAuth.loginWithAuthFileWithAuthResponse({
|
||||
filePath: authFilepath
|
||||
});
|
||||
console.log(authres);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,19 @@
|
|||
import * as msRestNodeAuth from "../lib/msRestNodeAuth";
|
||||
import * as dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const authFilepath = process.env.AUTH_FILE_SECRET_PATH || "";
|
||||
// copy the content of "sample.env" to a file named ".env". It should be stored at the root of the repo.
|
||||
// then from the root of the cloned repo run, ts-node ./samples/authFileWithSpSecret.ts
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const authres = await msRestNodeAuth.loginWithAuthFileWithAuthResponse({
|
||||
filePath: authFilepath
|
||||
});
|
||||
console.log(authres);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,15 @@
|
|||
import * as msRestNodeAuth from "../lib/msRestNodeAuth";
|
||||
import * as dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
// copy the content of "sample.env" to a file named ".env". It should be stored at the root of the repo.
|
||||
// then from the root of the cloned repo run, ts-node ./samples/interactive.ts
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const authres = await msRestNodeAuth.interactiveLogin();
|
||||
console.log(authres);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,23 @@
|
|||
import * as msRestNodeAuth from "../lib/msRestNodeAuth";
|
||||
import * as dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const clientId = process.env.CLIENT_ID || "";
|
||||
const tenantId = process.env.DOMAIN || "";
|
||||
const certificateFilepath = process.env.CERTIFICATE_FILE_PATH || "";
|
||||
// copy the content of "sample.env" to a file named ".env". It should be stored at the root of the repo.
|
||||
// then from the root of the cloned repo run, ts-node ./samples/servicePrincipalSecret.ts
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const authres = await msRestNodeAuth.loginWithServicePrincipalCertificateWithAuthResponse(
|
||||
clientId,
|
||||
certificateFilepath,
|
||||
tenantId
|
||||
);
|
||||
console.log(authres);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,23 @@
|
|||
import * as msRestNodeAuth from "../lib/msRestNodeAuth";
|
||||
import * as dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const clientId = process.env.CLIENT_ID || "";
|
||||
const tenantId = process.env.DOMAIN || "";
|
||||
const secret = process.env.APPLICATION_SECRET || "";
|
||||
// copy the content of "sample.env" to a file named ".env". It should be stored at the root of the repo.
|
||||
// then from the root of the cloned repo run, ts-node ./samples/servicePrincipalSecret.ts
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const authres = await msRestNodeAuth.loginWithServicePrincipalSecretWithAuthResponse(
|
||||
clientId,
|
||||
secret,
|
||||
tenantId
|
||||
);
|
||||
console.log(authres);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,21 @@
|
|||
import * as msRestNodeAuth from "../lib/msRestNodeAuth";
|
||||
import * as dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const username = process.env.AZURE_USERNAME || "";
|
||||
const password = process.env.AZURE_PASSWORD || "";
|
||||
// copy the content of "sample.env" to a file named ".env". It should be stored at the root of the repo.
|
||||
// then from the root of the cloned repo run, ts-node ./samples/usernamePassword.ts
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const authres = await msRestNodeAuth.loginWithUsernamePasswordWithAuthResponse(
|
||||
username,
|
||||
password
|
||||
);
|
||||
console.log(authres);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -35,6 +35,7 @@
|
|||
],
|
||||
"include": [
|
||||
"./lib/**/*.ts",
|
||||
"./test/**/*.ts"
|
||||
"./test/**/*.ts",
|
||||
"./samples/*.ts"
|
||||
]
|
||||
}
|
Загрузка…
Ссылка в новой задаче