applicationTokenCredentialsBase.ts 4.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See License.txt in the project root for license information.
  3. import { TokenCredentialsBase } from "./tokenCredentialsBase";
  4. import { Environment } from "@azure/ms-rest-azure-env";
  5. import { AuthConstants, TokenAudience } from "../util/authConstants";
  6. import { TokenCache, TokenResponse } from "adal-node";
  7. export abstract class ApplicationTokenCredentialsBase extends TokenCredentialsBase {
  8. /**
  9. * Creates a new ApplicationTokenCredentials object.
  10. * See {@link https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-dotnet/ Active Directory Quickstart for .Net}
  11. * for detailed instructions on creating an Azure Active Directory application.
  12. * @constructor
  13. * @param {string} clientId The active directory application client id.
  14. * @param {string} domain The domain or tenant id containing this application.
  15. * @param {string} [tokenAudience] The audience for which the token is requested. Valid values are 'graph', 'batch', or any other resource like 'https://vault.azure.net/'.
  16. * 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).
  17. * @param {Environment} [environment] The azure environment to authenticate with.
  18. * @param {object} [tokenCache] The token cache. Default value is the MemoryCache object from adal.
  19. */
  20. public constructor(
  21. clientId: string,
  22. domain: string,
  23. tokenAudience?: TokenAudience,
  24. environment?: Environment,
  25. tokenCache?: TokenCache
  26. ) {
  27. super(clientId, domain, tokenAudience, environment, tokenCache);
  28. }
  29. protected async getTokenFromCache(): Promise<TokenResponse> {
  30. const self = this;
  31. // a thin wrapper over the base implementation. try get token from cache, additionaly clean up cache if required.
  32. try {
  33. const tokenResponse = await super.getTokenFromCache(undefined);
  34. return Promise.resolve(tokenResponse);
  35. } catch (error) {
  36. // Remove the stale token from the tokencache. ADAL gives the same error message "Entry not found in cache."
  37. // for entry not being present in the cache and for accessToken being expired in the cache. We do not want the token cache
  38. // to contain the expired token, we clean it up here.
  39. const status = await self.removeInvalidItemsFromCache({
  40. _clientId: self.clientId
  41. });
  42. if (status.result) {
  43. return Promise.reject(error);
  44. }
  45. const message =
  46. status && status.details && status.details.message
  47. ? status.details.message
  48. : status.details;
  49. return Promise.reject(
  50. new Error(
  51. AuthConstants.SDK_INTERNAL_ERROR +
  52. " : " +
  53. "critical failure while removing expired token for service principal from token cache. " +
  54. message
  55. )
  56. );
  57. }
  58. }
  59. /**
  60. * Removes invalid items from token cache. This method is different. Here we never reject in case of error.
  61. * Rather we resolve with an object that says the result is false and error information is provided in
  62. * the details property of the resolved object. This is done to do better error handling in the above function
  63. * where removeInvalidItemsFromCache() is called.
  64. * @param {object} query The query to be used for finding the token for service principal from the cache
  65. * @returns {result: boolean, details?: Error} resultObject with more info.
  66. */
  67. private removeInvalidItemsFromCache(
  68. query: object
  69. ): Promise<{ result: boolean; details?: Error }> {
  70. const self = this;
  71. return new Promise<{ result: boolean; details?: Error }>(resolve => {
  72. self.tokenCache.find(query, (error: Error, entries: any[]) => {
  73. if (error) {
  74. return resolve({ result: false, details: error });
  75. }
  76. if (entries && entries.length > 0) {
  77. return new Promise(resolve => {
  78. return self.tokenCache.remove(entries, (err: Error) => {
  79. if (err) {
  80. return resolve({ result: false, details: err });
  81. }
  82. return resolve({ result: true });
  83. });
  84. });
  85. } else {
  86. return resolve({ result: true });
  87. }
  88. });
  89. });
  90. }
  91. }