{"size": 13651, "ext": "js", "max_stars_repo_path": "src/javascript/crypto/e2e/extension/ui/settings/settings.js", "max_stars_repo_name": "daniel-ziegler/end-to-end", "max_stars_repo_stars_event_min_datetime": null, "max_stars_repo_stars_event_max_datetime": null, "max_issues_repo_path": "src/javascript/crypto/e2e/extension/ui/settings/settings.js", "max_issues_repo_name": "daniel-ziegler/end-to-end", "max_issues_repo_issues_event_min_datetime": null, "max_issues_repo_issues_event_max_datetime": null, "max_forks_repo_path": "src/javascript/crypto/e2e/extension/ui/settings/settings.js", "max_forks_repo_name": "daniel-ziegler/end-to-end", "max_forks_repo_forks_event_min_datetime": null, "max_forks_repo_forks_event_max_datetime": null}
{"max_stars_count": null, "max_issues_count": null, "max_forks_count": null, "avg_line_length": 31.4539170507, "max_line_length": 80, "alphanum_fraction": 0.6790711303}
/**
 * @license
 * Copyright 2013 Google Inc. 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.
 */

/**
 * @fileoverview Provides the UI for the extension's settings page.
 */

goog.provide('e2e.ext.ui.Settings');

goog.require('e2e.cipher.Algorithm');
goog.require('e2e.ext.actions.Executor');
goog.require('e2e.ext.constants');
goog.require('e2e.ext.constants.Actions');
goog.require('e2e.ext.constants.ElementId');
goog.require('e2e.ext.ui.dialogs.Generic');
goog.require('e2e.ext.ui.dialogs.InputType');
goog.require('e2e.ext.ui.panels.GenerateKey');
goog.require('e2e.ext.ui.panels.KeyringMgmtFull');
goog.require('e2e.ext.ui.panels.PreferencesPanel');
goog.require('e2e.ext.ui.templates');
goog.require('e2e.ext.utils');
goog.require('e2e.ext.utils.Error');
goog.require('e2e.ext.utils.action');
goog.require('e2e.ext.utils.text');
goog.require('e2e.openpgp.asciiArmor');
goog.require('e2e.signer.Algorithm');
goog.require('goog.array');
goog.require('goog.crypt');
goog.require('goog.dom');
goog.require('goog.events.EventType');
goog.require('goog.structs.Map');
goog.require('goog.ui.Component');
goog.require('soy');

goog.scope(function() {
var ext = e2e.ext;
var constants = e2e.ext.constants;
var dialogs = e2e.ext.ui.dialogs;
var messages = e2e.ext.messages;
var panels = e2e.ext.ui.panels;
var preferences = e2e.ext.ui.preferences;
var templates = e2e.ext.ui.templates;
var ui = e2e.ext.ui;
var utils = e2e.ext.utils;



/**
 * Constructor for the settings page.
 * @constructor
 * @extends {goog.ui.Component}
 */
ui.Settings = function() {
  goog.base(this);

  /**
   * Executor for the End-to-End actions.
   * @type {!e2e.ext.actions.Executor}
   * @private
   */
  this.actionExecutor_ = new e2e.ext.actions.Executor(
      goog.bind(this.displayFailure_, this));

};
goog.inherits(ui.Settings, goog.ui.Component);


/**
 * The PGP context used by the extension.
 * @type {e2e.openpgp.ContextImpl}
 * @private
 */
ui.Settings.prototype.pgpContext_ = null;


/**
 * The panel to list and manage all stored PGP keys.
 * @type {panels.KeyringMgmtFull}
 * @private
 */
ui.Settings.prototype.keyringMgmtPanel_ = null;


/** @override */
ui.Settings.prototype.getContentElement = function() {
  return goog.dom.getElement(constants.ElementId.BODY) || this.getElement();
};


/** @override */
ui.Settings.prototype.decorateInternal = function(elem) {
  this.setElementInternal(elem);

  utils.action.getContext(function(pgpCtx) {
    this.pgpContext_ = pgpCtx;
    if (!this.pgpContext_.hasPassphrase()) {
      window.alert(chrome.i18n.getMessage('settingsKeyringLockedError'));
      window.close();
    } else {
      // TODO(radi): Move to an E2E action.
      this.pgpContext_.getAllKeys().
          addCallback(this.renderTemplate_, this).
          addErrback(this.displayFailure_, this);
    }
  }, this.displayFailure_, this);
};


/**
 * Renders the settings page.
 * @param {!Object} pgpKeys The existing PGP keys in the keyring.
 * @private
 */
ui.Settings.prototype.renderTemplate_ = function(pgpKeys) {
  var elem = this.getElement();

  soy.renderElement(elem, templates.settings, {
    pageTitle: chrome.i18n.getMessage('settingsTitle')
  });

  var styles = elem.querySelector('link');
  styles.href = chrome.runtime.getURL('settings_styles.css');

  var generateKeyPanel =
      new panels.GenerateKey(goog.bind(this.generateKey_, this));
  this.addChild(generateKeyPanel, true);

  this.keyringMgmtPanel_ = new panels.KeyringMgmtFull(
      pgpKeys,
      goog.bind(this.exportKeyring_, this),
      goog.bind(this.importKeyring_, this),
      goog.bind(this.updateKeyringPassphrase_, this),
      // TODO: Perhaps perform other actions on restore.
      goog.bind(this.renderNewKey_, this),
      goog.bind(this.exportKey_, this),
      goog.bind(this.removeKey_, this));
  this.addChild(this.keyringMgmtPanel_, true);
  this.keyringMgmtPanel_.setKeyringEncrypted(
      this.pgpContext_.isKeyRingEncrypted());

  var advancedLinkDiv = goog.dom.createElement(goog.dom.TagName.DIV);
  var advancedLink = goog.dom.createElement(goog.dom.TagName.A);
  advancedLink.href = '#';
  advancedLink.textContent = chrome.i18n.getMessage('showPreferencesLink');
  advancedLinkDiv.appendChild(advancedLink);
  this.getContentElement().appendChild(advancedLinkDiv);

  this.preferencesPanel_ = new panels.PreferencesPanel();
  this.addChild(this.preferencesPanel_, true);
  this.preferencesPanel_.getElement().style.display = 'none';

  this.getHandler().listen(advancedLink, goog.events.EventType.CLICK,
      goog.bind(function() {
        var prefs = this.preferencesPanel_.getElement();
        if (prefs.style.display === 'none') {
          advancedLink.textContent =
              chrome.i18n.getMessage('hidePreferencesLink');
          prefs.style.display = 'inherit';
        } else {
          advancedLink.textContent =
              chrome.i18n.getMessage('showPreferencesLink');
          prefs.style.display = 'none';
        }
      }, this));
  this.getHandler().listen(
      this.getElement(), goog.events.EventType.CLICK, this.clearFailure_);
};


/**
 * Gets lists of email addresses for keys in the keyring.
 * @param {string} type 'public' or 'private'
 * @param {!function(Array.<string>)} callback
 * @private
 */
ui.Settings.prototype.getEmails_ = function(type, callback) {
  utils.sendExtensionRequest(/** @type {messages.ApiRequest} */ ({
    action: constants.Actions.LIST_ALL_UIDS,
    content: type
  }), function(response) {
    response.content = response.content || [];
    var emails = utils.text.getValidEmailAddressesFromArray(response.content,
                                                            true);
    callback(emails);
  });
};


/**
 * Generates a new PGP key using the information that is provided by the user.
 * @param {panels.GenerateKey} panel The panel where the user has provided the
 *     information for the new key.
 * @param {string} name The name to use.
 * @param {string} email The email to use.
 * @param {string} comments The comments to use.
 * @param {number} expDate The expiration date to use.
 * @private
 */
ui.Settings.prototype.generateKey_ =
    function(panel, name, email, comments, expDate) {
  var normalizedEmail = utils.text.extractValidYahooEmail(email);
  if (!normalizedEmail) {
    alert(chrome.i18n.getMessage('invalidEmailWarning'));
    return null;
  }

  this.getEmails_('private', goog.bind(function(emails) {
    if (goog.array.contains(emails,
                            normalizedEmail.toLowerCase())) {
      alert(chrome.i18n.getMessage('duplicateKeyWarning'));
      return null;
    }

    var defaults = constants.KEY_DEFAULTS;
    return this.pgpContext_.generateKey(e2e.signer.Algorithm[defaults.keyAlgo],
        defaults.keyLength, e2e.cipher.Algorithm[defaults.subkeyAlgo],
        defaults.subkeyLength, name, comments, email, expDate)
        .addCallback(goog.bind(function(key) {
          // Key should be an array of exactly size 2 (one public, one private)
          panel.sendKeys(key, goog.bind(function(response) {
            this.renderNewKey_(key[0].uids[0]);
            panel.reset();
          }, this), this.pgpContext_);
        }, this)).addErrback(this.displayFailure_, this);
  }, this));
};


/**
 * Removes a PGP key.
 * @param {string} keyUid The ID of the key to remove.
 * @private
 */
ui.Settings.prototype.removeKey_ = function(keyUid) {
  this.pgpContext_
      .searchPrivateKey(keyUid)
      .addCallback(function(privateKeys) {
        // TODO(evn): This message should be localized.
        var prompt = 'Deleting all keys for ' + keyUid;
        if (privateKeys && privateKeys.length > 0) {
          prompt += '\n\nWARNING: This will delete some private keys!';
        }
        if (window.confirm(prompt)) {
          this.pgpContext_.deleteKey(keyUid);
          this.keyringMgmtPanel_.removeKey(keyUid);
        }
      }, this)
      .addErrback(this.displayFailure_, this);
};


/**
 * Exports a PGP key.
 * @param {string} keyUid The ID of the key to export.
 * @private
 */
ui.Settings.prototype.exportKey_ = function(keyUid) {
  this.pgpContext_.searchPublicKey(keyUid).addCallback(function(keys) {
    var armoredKey = e2e.openpgp.asciiArmor.encode(
        'PUBLIC KEY BLOCK',
        goog.array.flatten(goog.array.map(
            keys, function(key) {
          return key.serialized;
        })));
    var filename = keyUid.replace(/[\/\\]/g, '.') + '-public.asc';

    utils.writeToFile(
        armoredKey, function(fileUrl) {
          var anchor = document.createElement('a');
          anchor.download = filename;
          anchor.href = fileUrl;
          anchor.click();
        });
  }, this).addErrback(this.displayFailure_, this);
};


/**
 * Renders a new PGP key into the settings page.
 * @param {string} keyUid The key UID to render.
 * @private
 */
ui.Settings.prototype.renderNewKey_ = function(keyUid) {
  this.pgpContext_
      .searchKey(keyUid)
      .addCallback(function(pgpKeys) {
        this.keyringMgmtPanel_.addNewKey(keyUid, pgpKeys);
      }, this)
      .addErrback(this.displayFailure_, this);
};


/**
 * Imports a keyring from a file and appends it to the current keyring.
 * @param {!File} file The file to import.
 * @private
 */
ui.Settings.prototype.importKeyring_ = function(file) {
  utils.readFile(file, goog.bind(function(contents) {
    this.actionExecutor_.execute(/** @type {!messages.ApiRequest} */ ({
      action: constants.Actions.IMPORT_KEY,
      content: contents,
      passphraseCallback: goog.bind(this.renderPassphraseCallback_, this)
    }), this, goog.bind(function(res) {
      if (res.length > 0) {
        utils.showNotification(
            chrome.i18n.getMessage(
                'promptImportKeyNotificationLabel', res.toString()),
            goog.bind(function() {
              goog.array.forEach(res, function(keyUid) {
                this.renderNewKey_(keyUid);
              }, this);
              this.keyringMgmtPanel_.resetControls();
            }, this));
      } else {
        this.displayFailure_(new utils.Error(
            'Import key error', 'promptImportKeyError'));
      }
    }, this));
  }, this));
};


/**
 * Renders the UI elements needed for requesting the passphrase of an individual
 * PGP key.
 * @param {string} uid The UID of the PGP key.
 * @param {!function(string)} callback The callback to invoke when the
 *     passphrase has been provided.
 * @private
 */
ui.Settings.prototype.renderPassphraseCallback_ = function(uid, callback) {
  var popupElem = goog.dom.getElement(constants.ElementId.CALLBACK_DIALOG);
  var dialog = new dialogs.Generic(chrome.i18n.getMessage(
      'promptPassphraseCallbackMessage', uid),
      function(passphrase) {
        goog.dispose(dialog);
        callback(/** @type {string} */ (passphrase));
      },
      // Use a password field to ask for the passphrase.
      dialogs.InputType.SECURE_TEXT,
      '',
      chrome.i18n.getMessage('actionEnterPassphrase'),
      chrome.i18n.getMessage('actionCancelPgpAction'));

  this.addChild(dialog, false);
  dialog.render(popupElem);
};


/**
 * Exports the entire keyring to a file.
 * @private
 */
ui.Settings.prototype.exportKeyring_ = function() {
  var filename = (this.pgpContext_.isKeyRingEncrypted() ? '' : 'UNENCRYPTED-') +
      'keyring-private.asc';
  this.pgpContext_.exportKeyring(true).addCallback(function(armoredKey) {
    if (typeof armoredKey != 'string') {
      armoredKey = goog.crypt.byteArrayToString(armoredKey);
    }
    utils.writeToFile(
        armoredKey, function(fileUrl) {
          var anchor = document.createElement('a');
          anchor.download = filename;
          anchor.href = fileUrl;
          anchor.click();
        });
  }, this).addErrback(this.displayFailure_, this);
};


/**
 * Updates the passphrase to the existing keyring.
 * @param {string} passphrase The new passphrase to apply.
 * @private
 */
ui.Settings.prototype.updateKeyringPassphrase_ = function(passphrase) {
  this.pgpContext_.changeKeyRingPassphrase(passphrase);
  utils.showNotification(
      chrome.i18n.getMessage('keyMgmtChangePassphraseSuccessMsg'),
      goog.nullFunction);
  this.keyringMgmtPanel_.setKeyringEncrypted(
      this.pgpContext_.isKeyRingEncrypted());
};


/**
 * Displays an error message to the user.
 * @param {Error} error The error to display.
 * @private
 */
ui.Settings.prototype.displayFailure_ = function(error) {
  var errorDiv = goog.dom.getElement(constants.ElementId.ERROR_DIV);
  var errorMsg = goog.isDef(error.messageId) ?
      chrome.i18n.getMessage(error.messageId) : error.message;
  utils.errorHandler(error);
  errorDiv.textContent = errorMsg;
};


/**
 * Clears the error message notfication area.
 * @private
 */
ui.Settings.prototype.clearFailure_ = function() {
  var errorDiv = goog.dom.getElement(constants.ElementId.ERROR_DIV);
  errorDiv.textContent = '';
};


});  // goog.scope


// Create the settings page.
if (Boolean(chrome.extension)) {
  /** @type {!e2e.ext.ui.Settings} */
  window.settingsPage = new e2e.ext.ui.Settings();
  window.settingsPage.decorate(document.documentElement);
}
