summaryrefslogtreecommitdiffstats
path: root/services/fxaccounts/FxAccountsProfileClient.jsm
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /services/fxaccounts/FxAccountsProfileClient.jsm
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'services/fxaccounts/FxAccountsProfileClient.jsm')
-rw-r--r--services/fxaccounts/FxAccountsProfileClient.jsm260
1 files changed, 260 insertions, 0 deletions
diff --git a/services/fxaccounts/FxAccountsProfileClient.jsm b/services/fxaccounts/FxAccountsProfileClient.jsm
new file mode 100644
index 000000000..37115a3fa
--- /dev/null
+++ b/services/fxaccounts/FxAccountsProfileClient.jsm
@@ -0,0 +1,260 @@
+/* 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/. */
+
+/**
+ * A client to fetch profile information for a Firefox Account.
+ */
+ "use strict;"
+
+this.EXPORTED_SYMBOLS = ["FxAccountsProfileClient", "FxAccountsProfileClientError"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/FxAccountsCommon.js");
+Cu.import("resource://gre/modules/FxAccounts.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://services-common/rest.js");
+
+Cu.importGlobalProperties(["URL"]);
+
+/**
+ * Create a new FxAccountsProfileClient to be able to fetch Firefox Account profile information.
+ *
+ * @param {Object} options Options
+ * @param {String} options.serverURL
+ * The URL of the profile server to query.
+ * Example: https://profile.accounts.firefox.com/v1
+ * @param {String} options.token
+ * The bearer token to access the profile server
+ * @constructor
+ */
+this.FxAccountsProfileClient = function(options) {
+ if (!options || !options.serverURL) {
+ throw new Error("Missing 'serverURL' configuration option");
+ }
+
+ this.fxa = options.fxa || fxAccounts;
+ // This is a work-around for loop that manages its own oauth tokens.
+ // * If |token| is in options we use it and don't attempt any token refresh
+ // on 401. This is for loop.
+ // * If |token| doesn't exist we will fetch our own token. This is for the
+ // normal FxAccounts methods for obtaining the profile.
+ // We should nuke all |this.token| support once loop moves closer to FxAccounts.
+ this.token = options.token;
+
+ try {
+ this.serverURL = new URL(options.serverURL);
+ } catch (e) {
+ throw new Error("Invalid 'serverURL'");
+ }
+ this.oauthOptions = {
+ scope: "profile",
+ };
+ log.debug("FxAccountsProfileClient: Initialized");
+};
+
+this.FxAccountsProfileClient.prototype = {
+ /**
+ * {nsIURI}
+ * The server to fetch profile information from.
+ */
+ serverURL: null,
+
+ /**
+ * Interface for making remote requests.
+ */
+ _Request: RESTRequest,
+
+ /**
+ * Remote request helper which abstracts authentication away.
+ *
+ * @param {String} path
+ * Profile server path, i.e "/profile".
+ * @param {String} [method]
+ * Type of request, i.e "GET".
+ * @return Promise
+ * Resolves: {Object} Successful response from the Profile server.
+ * Rejects: {FxAccountsProfileClientError} Profile client error.
+ * @private
+ */
+ _createRequest: Task.async(function* (path, method = "GET") {
+ let token = this.token;
+ if (!token) {
+ // tokens are cached, so getting them each request is cheap.
+ token = yield this.fxa.getOAuthToken(this.oauthOptions);
+ }
+ try {
+ return (yield this._rawRequest(path, method, token));
+ } catch (ex) {
+ if (!ex instanceof FxAccountsProfileClientError || ex.code != 401) {
+ throw ex;
+ }
+ // If this object was instantiated with a token then we don't refresh it.
+ if (this.token) {
+ throw ex;
+ }
+ // it's an auth error - assume our token expired and retry.
+ log.info("Fetching the profile returned a 401 - revoking our token and retrying");
+ yield this.fxa.removeCachedOAuthToken({token});
+ token = yield this.fxa.getOAuthToken(this.oauthOptions);
+ // and try with the new token - if that also fails then we fail after
+ // revoking the token.
+ try {
+ return (yield this._rawRequest(path, method, token));
+ } catch (ex) {
+ if (!ex instanceof FxAccountsProfileClientError || ex.code != 401) {
+ throw ex;
+ }
+ log.info("Retry fetching the profile still returned a 401 - revoking our token and failing");
+ yield this.fxa.removeCachedOAuthToken({token});
+ throw ex;
+ }
+ }
+ }),
+
+ /**
+ * Remote "raw" request helper - doesn't handle auth errors and tokens.
+ *
+ * @param {String} path
+ * Profile server path, i.e "/profile".
+ * @param {String} method
+ * Type of request, i.e "GET".
+ * @param {String} token
+ * @return Promise
+ * Resolves: {Object} Successful response from the Profile server.
+ * Rejects: {FxAccountsProfileClientError} Profile client error.
+ * @private
+ */
+ _rawRequest: function(path, method, token) {
+ return new Promise((resolve, reject) => {
+ let profileDataUrl = this.serverURL + path;
+ let request = new this._Request(profileDataUrl);
+ method = method.toUpperCase();
+
+ request.setHeader("Authorization", "Bearer " + token);
+ request.setHeader("Accept", "application/json");
+
+ request.onComplete = function (error) {
+ if (error) {
+ return reject(new FxAccountsProfileClientError({
+ error: ERROR_NETWORK,
+ errno: ERRNO_NETWORK,
+ message: error.toString(),
+ }));
+ }
+
+ let body = null;
+ try {
+ body = JSON.parse(request.response.body);
+ } catch (e) {
+ return reject(new FxAccountsProfileClientError({
+ error: ERROR_PARSE,
+ errno: ERRNO_PARSE,
+ code: request.response.status,
+ message: request.response.body,
+ }));
+ }
+
+ // "response.success" means status code is 200
+ if (request.response.success) {
+ return resolve(body);
+ } else {
+ return reject(new FxAccountsProfileClientError({
+ error: body.error || ERROR_UNKNOWN,
+ errno: body.errno || ERRNO_UNKNOWN_ERROR,
+ code: request.response.status,
+ message: body.message || body,
+ }));
+ }
+ };
+
+ if (method === "GET") {
+ request.get();
+ } else {
+ // method not supported
+ return reject(new FxAccountsProfileClientError({
+ error: ERROR_NETWORK,
+ errno: ERRNO_NETWORK,
+ code: ERROR_CODE_METHOD_NOT_ALLOWED,
+ message: ERROR_MSG_METHOD_NOT_ALLOWED,
+ }));
+ }
+ });
+ },
+
+ /**
+ * Retrieve user's profile from the server
+ *
+ * @return Promise
+ * Resolves: {Object} Successful response from the '/profile' endpoint.
+ * Rejects: {FxAccountsProfileClientError} profile client error.
+ */
+ fetchProfile: function () {
+ log.debug("FxAccountsProfileClient: Requested profile");
+ return this._createRequest("/profile", "GET");
+ },
+
+ /**
+ * Retrieve user's profile from the server
+ *
+ * @return Promise
+ * Resolves: {Object} Successful response from the '/avatar' endpoint.
+ * Rejects: {FxAccountsProfileClientError} profile client error.
+ */
+ fetchProfileImage: function () {
+ log.debug("FxAccountsProfileClient: Requested avatar");
+ return this._createRequest("/avatar", "GET");
+ }
+};
+
+/**
+ * Normalized profile client errors
+ * @param {Object} [details]
+ * Error details object
+ * @param {number} [details.code]
+ * Error code
+ * @param {number} [details.errno]
+ * Error number
+ * @param {String} [details.error]
+ * Error description
+ * @param {String|null} [details.message]
+ * Error message
+ * @constructor
+ */
+this.FxAccountsProfileClientError = function(details) {
+ details = details || {};
+
+ this.name = "FxAccountsProfileClientError";
+ this.code = details.code || null;
+ this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
+ this.error = details.error || ERROR_UNKNOWN;
+ this.message = details.message || null;
+};
+
+/**
+ * Returns error object properties
+ *
+ * @returns {{name: *, code: *, errno: *, error: *, message: *}}
+ * @private
+ */
+FxAccountsProfileClientError.prototype._toStringFields = function() {
+ return {
+ name: this.name,
+ code: this.code,
+ errno: this.errno,
+ error: this.error,
+ message: this.message,
+ };
+};
+
+/**
+ * String representation of a profile client error
+ *
+ * @returns {String}
+ */
+FxAccountsProfileClientError.prototype.toString = function() {
+ return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
+};