azure-cli
Version:
Microsoft Azure Cross Platform Command Line tool
219 lines (197 loc) • 8.1 kB
JavaScript
/**
* 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.
*/
var util = require('util');
var async = require('async');
var __ = require('underscore');
var constants = require('../constants.js');
var Subscription = require('./subscription.js');
var utils = require('../utils');
function Account(env, adalAuth, resourceClient, log) {
this._env = env;
this._adalAuth = adalAuth;
this._resourceClient = resourceClient;
this._log = log;
this.WarnToUserInteractiveFieldName = 'warnToUserInteractive';
}
Account.prototype.load = function (username, password, tenant, loginType, callback) {
var self = this;
if (!tenant) {
tenant = constants.AAD_COMMON_TENANT;
}
if (username) {
username = self._adalAuth.normalizeUserName(username);
}
self._username = username;
var userType = 'user';
if (loginType.servicePrincipal) {
userType = 'servicePrincipal';
}
function processAndReturnSubscriptions(err, result) {
if (err) return callback(err);
var subscriptions = result.subscriptions;
var subs = __.map(subscriptions, function (s) {
return new Subscription({
id: s.subscriptionId,
name: s.displayName || s.subscriptionName,
user: {
name: self._username,
type: userType
},
state: s.state,
tenantId: s.tenantId ? s.tenantId : tenant
}, self._env);
});
callback(null, { subscriptions: subs, tenantIds: result.tenantIds });
}
if (loginType.servicePrincipal) {
this._adalAuth.createServicePrincipalTokenCredentials(self._env.getAuthConfig(tenant), username, password, function (err, credential) {
if (err) { return callback(err); }
self._getSubscriptionsFromTenants(username, [tenant], credential, function (err, subscriptions) {
if (err) return callback(err);
return processAndReturnSubscriptions(null, { subscriptions: subscriptions });
});
});
} else if (loginType.interactive) {
self._getSubscriptionsInteractive(tenant, processAndReturnSubscriptions);
} else {
self._getSubscriptionsNonInteractive(username, password, tenant, processAndReturnSubscriptions);
}
};
Account.prototype._getSubscriptionsInteractive = function (tenant, callback) {
var self = this;
var authConfig = self._env.getAuthConfig(tenant);
async.waterfall([
function (callback) {
self._adalAuth.acquireUserCode(authConfig, function (err, userCodeResponse) {
if (err) return callback(err);
return callback(null, userCodeResponse);
});
},
function (userCodeResponse, callback) {
self._log.info(userCodeResponse.message);
self._log.verbose('code response from AAD is :' + JSON.stringify(userCodeResponse));
self._adalAuth.authenticateWithDeviceCode(authConfig, userCodeResponse, function (err, credential) {
if (err) return callback(err);
self._username = credential.userId;
async.waterfall([
function (callback) {
self._buildTenantList(self._username, tenant, credential, callback);
},
function (tenantList, callback) {
self._getSubscriptionsFromTenants(self._username, tenantList, null, callback);
},
], function (err, subscriptions) {
callback(err, { subscriptions : subscriptions });
});
});
}
], function (err, subscriptions) {
callback(err, subscriptions);
});
};
Account.prototype._getSubscriptionsNonInteractive = function (username, password, tenant, callback) {
var self = this;
self._adalAuth.authenticateWithUsernamePassword(self._env.getAuthConfig(tenant), username, password, function (err, credential) {
if (err && err.message && self._errorSuggestsInteractiveFlow(err.message)) {
err[self.WarnToUserInteractiveFieldName] = true;
}
if (err) return callback(err);
username = _crossCheckUserNameWithToken(username, credential.userId);
async.waterfall([
function (callback) {
self._buildTenantList(username, tenant, credential, callback);
},
function (tenantList, callback) {
self._getSubscriptionsFromTenants(username, tenantList, null, callback);
},
], function (err, subscriptions) {
callback(err, { subscriptions : subscriptions });
});
});
};
Account.prototype._errorSuggestsInteractiveFlow = function (errMessage) {
var codes = ['50072', '50074', '50076', '50077', '50078', '50079'];
var contains2FAErrCode = __.any(codes, function (code) {
return (errMessage.indexOf('AADSTS' + code) !== -1);
});
var containsErrCausedByUsingMsa = utils.ignoreCaseEquals(errMessage, 'Server returned an unknown AccountType: undefined') ||
utils.ignoreCaseEquals(errMessage, 'Server returned error in RSTR - ErrorCode: NONE : FaultMessage: NONE');
return contains2FAErrCode || containsErrCausedByUsingMsa;
};
Account.prototype._polishError = function (errMessage) {
if (utils.ignoreCaseEquals(errMessage, 'Server returned an unknown AccountType: undefined') ||
utils.ignoreCaseEquals(errMessage, 'Server returned error in RSTR - ErrorCode: NONE : FaultMessage: NONE')) {
errMessage = 'Your account type requires to login using interactive flow. Please rerun "login" command without -u/--username parameter';
}
return errMessage;
};
Account.prototype._getSubscriptionsFromTenants = function (username, tenantList, preBuiltCredential, callback) {
var self = this;
var subscriptions = [];
async.eachSeries(tenantList, function (tenant, cb) {
var credential = preBuiltCredential;
if (!credential) {
credential = new self._adalAuth.UserTokenCredentials(self._env.getAuthConfig(tenant), username);
}
var armClient = self._getArmClient(credential);
armClient.subscriptions.list(function (err, result) {
if (!err) {
subscriptions = subscriptions.concat(result.map(function (s) {
s.tenantId = tenant;
s.username = username;
return s;
}));
}
cb(err);
});
}, function (err) {
callback(err, subscriptions);
});
};
Account.prototype._buildTenantList = function (username, tenant, credential, callback) {
var self = this;
var tenants = [];
if (tenant && tenant !== constants.AAD_COMMON_TENANT) {
return callback(null, [tenant]);
}
self._getAllTenants(credential, function (err, result) {
if (err) return callback(err);
async.eachSeries(result, function (tenantInfo, cb) {
tenants.push(tenantInfo.tenantId);
cb(err);
}, function (err) {
callback(err, tenants);
});
});
};
Account.prototype._getAllTenants = function (credentials, callback) {
var armClient = this._getArmClient(credentials);
armClient.tenants.list(callback);
};
Account.prototype._getArmClient = function (credentials) {
return new this._resourceClient.SubscriptionClient(credentials,
this._env.resourceManagerEndpointUrl);
};
module.exports = Account;
function _crossCheckUserNameWithToken(usernameFromCommandline, userIdFromToken) {
//to maintain the casing consistency between 'azureprofile.json' and token cache. (RD 1996587)
//use the 'userId' here, which should be the same with "username" except the casing.
if (utils.ignoreCaseEquals(usernameFromCommandline, userIdFromToken)) {
return userIdFromToken;
} else {
throw new Error(util.format('The userId of \'%s\' in access token doesn\'t match the command line username of \'%s\'',
userIdFromToken, usernameFromCommandline));
}
}