UNPKG

azure-cli

Version:

Microsoft Azure Cross Platform Command Line tool

195 lines (174 loc) 6.37 kB
/** * Copyright (c) Microsoft. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; var _ = require('underscore'); var async = require('async'); //Skip fields which aren't required for token acquisitions, but causing token //persisting issues, such as https://github.com/Azure/azure-xplat-cli/issues/2269 var fieldsOmittedFromCache = ['familyName', 'givenName', 'isUserIdDisplayable', 'tenantId']; /** * Constructs a new token cache that works with adal-node * @constructor */ function TokenCache(tokenStorage) { this._entries = null; this._tokenStorage = tokenStorage; } _.extend(TokenCache.prototype, { /** * Load the cache entries. Does a lazy load, * loads from OS on first request, otherwise * returns in-memory copy. * * @param {function(err, Array)} callback callback * receiving cache entries. */ _loadEntries: function (callback) { var self = this; if (self._entries !== null) { return callback(null, self._entries); } self._tokenStorage.loadEntries(function (err, entries) { if (!err) { self._entries = entries; } self._normalize(entries, false); callback(err, entries); }); }, _normalize: function (entries, removeUselessFields) { entries.forEach(function (entry) { if (entry.userId) { entry.userId = entry.userId.toLowerCase(); } if (removeUselessFields) { fieldsOmittedFromCache.forEach(function (field) { delete entry[field]; }); } }); }, isSecureCache: function () { return this._tokenStorage.isSecureCache; }, /** * Removes a collection of entries from the cache in a single batch operation. * @param {Array} entries An array of cache entries to remove. * @param {Function} callback This function is called when the operation is complete. Any error is provided as the * first parameter. */ remove: function remove(entries, callback) { var self = this; //clone it so '_normalize' won't impact original cached entries var clonedList = []; entries.forEach(function(e){ clonedList.push(_.clone(e)); }); self._normalize(clonedList, true); function shouldKeep(entry) { //Note, '_findWhere' doesn't do deep comparision, so exlcude fields with object type var matched = _.findWhere(clonedList, _.omit(entry, ['expiresOn'].concat(fieldsOmittedFromCache))); return (!matched); } self._loadEntries(function (err, _entries) { if (err) { return callback(err); } var grouped = _.groupBy(_entries, shouldKeep); var entriesToRemove = grouped[false] || []; var entriesToKeep = grouped[true] || []; self._tokenStorage.removeEntries(entriesToRemove, entriesToKeep, function (err) { if (!err) { self._entries = entriesToKeep; } callback(err); }); }); }, /** * Clears a collection of entries from the cache in a single batch operation. * @param {Function} callback This function is called when the operation is complete. Any error is provided as the * first parameter. */ clear: function clear(callback) { this._tokenStorage.clear(callback); }, /** * Adds a collection of entries to the cache in a single batch operation. * @param {Array} entries An array of entries to add to the cache. * @param {Function} callback This function is called when the operation is complete. Any error is provided as the * first parameter. */ add: function add(newEntries, callback) { var self = this; self._normalize(newEntries, true); async.waterfall([ //load existing entries function (cb) { self._loadEntries(cb); }, //clean up entries with same fields. function (existingEntries, cb) { //'self._entries' should be the same with existingEntries async.eachSeries(newEntries, function (e, cb2) { var query = { _clientId: e._clientId, userId: e.userId, _authority: e._authority }; self.find(query, function (err, result) { if (result && result.length !== 0) { return self.remove(result, cb2); } return cb2(err); }); }, function (err) { return cb(err); } ); }, //add all newEntries function (cb) { self._tokenStorage.addEntries(newEntries, self._entries, function (err) { if (err) return cb(err); newEntries.forEach(function (entry) { self._entries.push(entry); }); return cb(null); }); } ], function (err) { return callback(err); }); }, /** * Finds all entries in the cache that match all of the passed in values. * @param {object} query This object will be compared to each entry in the cache. Any entries that * match all of the values in this object will be returned. All the values * in the passed in object must match values in a potentialy returned object * exactly. The returned object may have more values than the passed in query * object. * @param {TokenCacheFindCallback} callback */ find: function find(query, callback) { var self = this; self._normalize([query], true); self._loadEntries(function (err, _entries) { if (err) { return callback(err); } var results = _.where(_entries, query); callback(null, results); }); } }); module.exports = TokenCache;