Files
2020-08-08 11:09:26 +08:00

298 lines
9.2 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = ["cloudFileAccounts"];
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var Cr = Components.results;
var CATEGORY = "cloud-files";
var PREF_ROOT = "mail.cloud_files.";
var ACCOUNT_ROOT = PREF_ROOT + "accounts.";
// The following constants are used to query and insert entries
// into the nsILoginManager.
var PWDMGR_HOST = "chrome://messenger/cloudfile";
var PWDMGR_REALM = "BigFiles Auth Token";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/iteratorUtils.jsm");
var cloudFileAccounts = {
get kTokenRealm() {
return PWDMGR_REALM;
},
get _accountKeys() {
let accountKeySet = {};
let branch = Services.prefs.getBranch(ACCOUNT_ROOT);
let children = branch.getChildList("", {});
for (let child of children) {
let subbranch = child.substr(0, child.indexOf("."));
accountKeySet[subbranch] = 1;
}
// TODO: sort by ordinal
return Object.keys(accountKeySet);
},
_getInitedProviderForType: function(aAccountKey, aType) {
let provider = this.getProviderForType(aType);
if (provider) {
try {
provider.init(aAccountKey);
} catch (e) {
Components.utils.reportError(e);
provider = null;
}
}
return provider;
},
_createUniqueAccountKey: function() {
// Pick a unique account key (TODO: this is a dumb way to do it, probably)
let existingKeys = this._accountKeys;
for (let n = 1; ; n++) {
if (!existingKeys.includes("account" + n))
return "account" + n;
}
},
/**
* Ensure that we have the account key for an account. If we already have the
* key, just return it. If we have the nsIMsgCloudFileProvider, get the key
* from it.
*
* @param aKeyOrAccount the key or the account object
* @return the account key
*/
_ensureKey: function(aKeyOrAccount) {
if (typeof aKeyOrAccount == "string")
return aKeyOrAccount;
else if ("accountKey" in aKeyOrAccount)
return aKeyOrAccount.accountKey;
else
throw new Error("string or nsIMsgCloudFileProvider expected");
},
getProviderForType: function(aType) {
try {
let className = categoryManager.getCategoryEntry(CATEGORY, aType);
let provider = Cc[className].createInstance(Ci.nsIMsgCloudFileProvider);
return provider;
} catch (e) {
if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
// If a provider is not available we swallow the error message.
// Otherwise at least notify, so developers can fix things.
Cu.reportError("Getting provider for type=" + aType + " FAILED; " + e);
}
}
return null;
},
// aExtraPrefs are prefs specific to an account provider.
createAccount: function(aType, aRequestObserver, aExtraPrefs) {
let key = this._createUniqueAccountKey();
try {
Services.prefs
.setCharPref(ACCOUNT_ROOT + key + ".type", aType);
if (aExtraPrefs !== undefined)
this._processExtraPrefs(key, aExtraPrefs);
let provider = this._getInitedProviderForType(key, aType);
if (provider)
provider.createExistingAccount(aRequestObserver);
return provider;
}
catch(e) {
Services.prefs.deleteBranch(ACCOUNT_ROOT + key);
throw e;
}
},
// Set provider-specific prefs
_processExtraPrefs: function CFA__processExtraPrefs(aAccountKey,
aExtraPrefs) {
const kFuncMap = {
"int": "setIntPref",
"bool": "setBoolPref",
"char": "setCharPref",
};
for (let prefKey in aExtraPrefs) {
let type = aExtraPrefs[prefKey].type;
let value = aExtraPrefs[prefKey].value;
if (!(type in kFuncMap)) {
Components.utils.reportError("Did not recognize type: " + type);
continue;
}
let func = kFuncMap[type];
Services.prefs[func](ACCOUNT_ROOT + aAccountKey + "." + prefKey,
value);
}
},
enumerateProviders: function*() {
for (let entry in fixIterator(categoryManager.enumerateCategory(CATEGORY),
Ci.nsISupportsCString)) {
let provider = this.getProviderForType(entry.data);
yield [entry.data, provider];
}
},
getAccount: function(aKey) {
let type = Services.prefs.QueryInterface(Ci.nsIPrefBranch)
.getCharPref(ACCOUNT_ROOT + aKey + ".type");
return this._getInitedProviderForType(aKey, type);
},
removeAccount: function(aKeyOrAccount) {
let key = this._ensureKey(aKeyOrAccount);
let type = Services.prefs.QueryInterface(Ci.nsIPrefBranch)
.deleteBranch(ACCOUNT_ROOT + key);
// Destroy any secret tokens for this accountKey.
let logins = Services.logins
.findLogins({}, PWDMGR_HOST, null, "");
for (let login of logins) {
if (login.username == key)
Services.logins.removeLogin(login);
}
},
get accounts() {
return this._accountKeys.filter(key => this.getAccount(key) != null).
map(key => this.getAccount(key));
},
getAccountsForType: function CFA_getAccountsForType(aType) {
let result = [];
for (let accountKey of this._accountKeys) {
let type = Services.prefs.getCharPref(ACCOUNT_ROOT + accountKey + ".type");
if (type === aType)
result.push(this.getAccount(accountKey));
}
return result;
},
addAccountDialog: function CFA_addAccountDialog() {
let params = {accountKey: null};
Services.wm
.getMostRecentWindow(null)
.openDialog("chrome://messenger/content/cloudfile/"
+ "addAccountDialog.xul",
"", "chrome, dialog, modal, resizable=yes",
params).focus();
return params.accountKey;
},
getDisplayName: function(aKeyOrAccount) {
try {
let key = this._ensureKey(aKeyOrAccount);
return Services.prefs.getCharPref(ACCOUNT_ROOT +
key + ".displayName");
} catch(e) {
// If no display name has been set, we return the empty string.
Components.utils.reportError(e);
return "";
}
},
setDisplayName: function(aKeyOrAccount, aDisplayName) {
let key = this._ensureKey(aKeyOrAccount);
Services.prefs.setCharPref(ACCOUNT_ROOT + key +
".displayName", aDisplayName);
},
/**
* Retrieve a secret value, like an authorization token, for an account.
*
* @param aKeyOrAccount an nsIMsgCloudFileProvider, or an accountKey
* for a provider.
* @param aRealm a human-readable string describing what exactly
* was being stored. Should match the realm used when setting
* the value.
*/
getSecretValue: function(aKeyOrAccount, aRealm) {
let key = this._ensureKey(aKeyOrAccount);
let loginInfo = this._getLoginInfoForKey(key, aRealm);
if (loginInfo)
return loginInfo.password;
return null;
},
/**
* Store a secret value, like an authorization token, for an account
* in nsILoginManager.
*
* @param aKeyOrAccount an nsIMsgCloudFileProvider, or an accountKey
* for a provider.
* @param aRealm a human-readable string describing what exactly
* is being stored here. To reduce magic strings, you can use
* cloudFileAccounts.kTokenRealm for simple auth tokens, and
* anything else for custom secret values.
* @param aToken The token to be saved. If this is set to null or the
* empty string, then the entry for this key will be removed.
*/
setSecretValue: function(aKeyOrAccount, aRealm, aToken) {
let key = this._ensureKey(aKeyOrAccount);
let loginInfo = this._getLoginInfoForKey(key, aRealm);
if (!aToken) {
if (!loginInfo)
return;
Services.logins.removeLogin(loginInfo);
return;
}
let newLoginInfo = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
newLoginInfo.init(PWDMGR_HOST, null, aRealm, key,
aToken, "", "");
if (loginInfo)
Services.logins.modifyLogin(loginInfo, newLoginInfo);
else
Services.logins.addLogin(newLoginInfo);
},
/**
* Searches the nsILoginManager for an nsILoginInfo for BigFiles with
* the username set to aKey, and the realm set to aRealm.
*
* @param aKey a key for an nsIMsgCloudFileProvider that we're searching
* for login info for.
* @param aRealm the realm that the login info was stored under.
*/
_getLoginInfoForKey: function(aKey, aRealm) {
let logins = Services.logins
.findLogins({}, PWDMGR_HOST, null, aRealm);
for (let login of logins) {
if (login.username == aKey)
return login;
}
return null;
},
};
XPCOMUtils.defineLazyServiceGetter(this, "categoryManager",
"@mozilla.org/categorymanager;1",
"nsICategoryManager");