<commit_before>function (user, context, callback) {
  // Imports
  const request = require('request');
  const YAML = require('js-yaml');
  const jose = require('node-jose');

  // Retrieve the access file information/configuration from well-known
  // See also https://github.com/mozilla-iam/cis/blob/profilev2/docs/.well-known/mozilla-iam.json
  function get_access_file_configuration(cb) {
    var access_file_conf = {};
    var options = { method: 'GET', url: configuration.iam_well_known };
    request(options, function (error, response, body) {
      if (error) throw new Error(error);
      if (response.statusCode !== 200) {
        console.log('Could not fetch access file URL: '+response.statusCode);
      } else {
        access_file_conf = JSON.parse(body).access_file;
        // contains mainly:
        // access_file_conf.endpoint  (URL)
        // access_file_conf.jwks.keys[]{} (pub keys)
        // access_file_conf.aai_mappings
      }
      return cb(access_file_conf);
    });
  }

  // Retrieve and verify access rule file itself
  function get_verified_access_rules(cb, access_file_conf) {
    // Bypass if we have a cached version present already
    // Cache is very short lived in webtask, it just means we hit a "hot" task which nodejs process hasn't yet been
    // terminated. Generally this means we hit the same task within 60s.
    if (global.access_rules) {
      return cb(global.access_rules, access_file_conf);
    }

    var options = { method: 'GET', url: access_file_conf.endpoint };
    var decoded;
    request(options, function (error, response, body) {
      if (error) throw new Error(error);
      // Convert key into jose-formatted-key
      // XXX remove this part of the code when well-known and signature exists
      if (access_file_conf.jwks === null) {
        console.log('WARNING: Bypassing access file signature verification');
        decoded = body;
      } else {
        // XXX verify key format when the well-known endpoint exists
        var pubkey = jose.JWK.asKey(access_file_conf.jwks.keys.x5c[0], 'pem').then((jwk) => jwk);
        var verifier = jose.JWS.createVerify(pubkey);
        var ret = verifier.verify(body).then((response) => response.payload).catch((err) => err);
        decoded = ret.then((data) => data).catch((err) => {
          throw new Error('Signature verification of access file failed (fatal): '+err);
        });
      }

      global.access_rules = YAML.load(decoded).apps;
      return cb(global.access_rules, access_file_conf);
    });
  }

  // Check if array A has any occurrence from array B
  function array_in_array(A, B) {
    var found = A.some(
      function(item) {
        if (!B)
          return false;
        return B.indexOf(item) >= 0;
    });
    return found;
  }

  // Update expiration and grant access
  function access_granted(a, b, c) {
    updateAccessExpiration();
    return callback(a, b, c);
  }

  // Deny access
  function access_denied(a, b, c) {
    return callback(a, b, c);
  }

  // updateAccessExpiration()
  // Always returns - will attempt to update user.app_metadata.authoritativeGroups[].lastUsed timestamp
  // for the RP/client_id we're currently trying to login to
  // XXX Use Profilev2 when available
  function updateAccessExpiration() {
      user.app_metadata = user.app_metadata || {};
      if (user.app_metadata.authoritativeGroups === undefined) {
          console.log('ExpirationOfAccess: not enabled for this user');
          return;
      }

      var updated = false;
      for (var index = 0;index < user.app_metadata.authoritativeGroups.length;++index) {
        if (user.app_metadata.authoritativeGroups[index].uuid === context.clientID) {
          user.app_metadata.authoritativeGroups[index].lastUsed = new Date();
          updated = true;
          break; // we're done
        }
      }
      if (updated === true) {
        auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
          .catch(function(err) {
          console.log('ExpirationOfAccess: Error updating app_metadata (AuthoritativeGroups) for user '+user.user_id+': '+err);
        });
      }
      console.log('ExpirationOfAccess: Updated lastUsed for '+user.user_id);
      return;
  }

  // Process the access cache decision
  function access_decision(access_rules, access_file_conf) {
    // Use whatever is available from the group struct. Sometimes there's a race condition where user.app_metadata.*
    // isnt reintegrated to user.* for example
    var groups = user.app_metadata.groups || user.groups || [];
    // This is used for authorized user/groups
    var authorized = false;
    // Defaut app requested aal to MEDIUM for all apps which do not have this set in access file
    var required_aal = "MEDIUM";

    for (var i=0; i<access_rules.length; i++) {
      var app = access_rules[i].application;

      //Handy for quick testing in dev (overrides access rules)
      //var app = {'client_id': 'pCGEHXW0VQNrQKURDcGi0tghh7NwWGhW', // This is testrp social-ldap-pwless
      //           'authorized_users': ['gdestuynder@mozilla.com'],
      //           'authorized_groups': ['okta_mfa'],
      //           'expire_access_when_unused_after': 86400,
      //           'aal': 'LOW'
      //          };

      if (app.client_id && (app.client_id.indexOf(context.clientID) >= 0)) {
        // Set app AAL (AA level) if present
        required_aal = app.AAL || required_aal;

        // EXPIRATION OF ACCESS
        // Note that the expiration check MUST always run first
        // Check if the user access to the RP has expired due to ExpirationOfAccess
        if ((app.expire_access_when_unused_after !== undefined) && (app.expire_access_when_unused_after > 0)) {
          user.app_metadata = user.app_metadata || {};
          // If the user has no authoritativeGroups for this clientID, let the user in
          if (user.app_metadata.authoritativeGroups !== undefined) {
            for (var index=0;index < user.app_metadata.authoritativeGroups.length; ++index) {
              if (user.app_metadata.authoritativeGroups[index].uuid === context.clientID) {
                // Find the delta for this user and see if access should have expired
                var lastUsed_ts = new Date(user.app_metadata.authoritativeGroups[index].lastUsed).getTime();
                var delta = new Date().getTime() - lastUsed_ts;
                // Access expired?
                if (delta > app.expire_access_when_unused_after) {
                    // Do not allow the user in, no matter what other access has been set
                    console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - access has expired");
                    return access_denied(null, user, global.postError('accesshasexpired', context));
                }
                break;
              }
            }
          }
        }

        // AUTHORIZED_{GROUPS,USERS}
        // XXX this authorized_users SHOULD BE REMOVED as it's unsafe (too easy to make mistakes). USE GROUPS.
        // XXX This needs to be fixed in the dashboard first
        // Empty users or groups (length == 0) means no access in the dashboard apps.yml world
        if (app.authorized_users.length === app.authorized_groups.length === 0) {
          console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - this app denies ALL users and ALL groups");
          return access_denied(null, user, global.postError('notingroup', context));
        }

        // Check if the user is authorized to access
        if ((app.authorized_users.length > 0 ) && (app.authorized_users.indexOf(user.email) >= 0)) {
          authorized = true;
        // Same dance as above, but for groups
        } else if ((app.authorized_groups.length > 0) && array_in_array(app.authorized_groups, groups)) {
          authorized = true;
        } else {
          authorized = false;
        }

        if (!authorized) {
          console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - not in authorized group or not an authorized user");
          return access_denied(null, user, global.postError('notingroup', context));
        }
      } // correct client id / we matched the current RP
    } // for loop / next rule in apps.yml

    // AAI (AUTHENTICATOR ASSURANCE INDICATOR) REQUIREMENTS
    //
    // Note that user.aai is set in another rule (rules/aai.js)
    // This file sets the user.aal (authenticator assurance level) which is the result of a map lookup against user.aai
    //
    // Mapping logic and verification
    // Ex: our mapping says 2FA for MEDIUM AAL and app AAL is MEDIUM as well, and the user has 2FA AAI, looks like:
    // access_file_conf.aai_mapping['MEDIUM'] = ['2FA'];
    // app.AAL = 'MEDIUM;
    // user.aai = ['2FA'];
    // Thus user should be allowed for this app (it requires MEDIUM, and MEDIUM requires 2FA, and user has 2FA
    // indeed)

    var aai_pass = false;
    if ((access_file_conf.aai_mapping !== undefined) && (access_file_conf.aai_mapping[required_aal].length === 0)) {
      // No required indicator in aai_mapping for this app's requested AAIs
      aai_pass = true;
    } else {
      for (var y=0; y<user.aai.length; y++) {
        var this_aai = user.aai[y];
        if (access_file_conf.aai_mapping[required_aal].indexOf(this_aai) >= 0) {
          aai_pass = true;
          break;
        }
      }
    }

    if (!aai_pass) {
      console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - due to " +
        "Identity Assurance Verification being too low for this RP. Required AAI: "+required_aal+
        " ("+aai_pass+")");
      return access_denied(null, user, global.postError('aai_failed', context));
    } else {
      // Inform RPs of which AAL let the user in
      var namespace = 'https://sso.mozilla.com/claim/';
      context.idToken[namespace+"AAL"] = required_aal;
    }

    // We matched no rule, access is granted
    return access_granted(null, user, context);
  }

  // "Main" starts here
  // This is a fake access file conf similar to the well-known endpoint,
  // until the well-known endpoint is actually available - it is also not signed and disables signing verification
  const fake_access_file_conf = { endpoint: 'https://cdn.sso.mozilla.com/apps.yml', jwks: null,
    aai_mapping: {
      "LOW": [],
      "MEDIUM": ["2FA", "HIGH_ASSURANCE_IDP"],
      "HIGH": [],
      "MAXIMUM": []
    }
  };
  // Get access file configuration, then get the access rules (apps.yml), then give all this to the access_decision()
  // function to decide if the user should be allowed or not. Any failure along the way will forbid the user to get in.
  return get_access_file_configuration(function(access_file_conf) {
    return get_verified_access_rules(access_decision, fake_access_file_conf);
  });
}
<commit_msg>typo in log message<commit_after>function (user, context, callback) {
  // Imports
  const request = require('request');
  const YAML = require('js-yaml');
  const jose = require('node-jose');

  // Retrieve the access file information/configuration from well-known
  // See also https://github.com/mozilla-iam/cis/blob/profilev2/docs/.well-known/mozilla-iam.json
  function get_access_file_configuration(cb) {
    var access_file_conf = {};
    var options = { method: 'GET', url: configuration.iam_well_known };
    request(options, function (error, response, body) {
      if (error) throw new Error(error);
      if (response.statusCode !== 200) {
        console.log('Could not fetch access file URL: '+response.statusCode);
      } else {
        access_file_conf = JSON.parse(body).access_file;
        // contains mainly:
        // access_file_conf.endpoint  (URL)
        // access_file_conf.jwks.keys[]{} (pub keys)
        // access_file_conf.aai_mappings
      }
      return cb(access_file_conf);
    });
  }

  // Retrieve and verify access rule file itself
  function get_verified_access_rules(cb, access_file_conf) {
    // Bypass if we have a cached version present already
    // Cache is very short lived in webtask, it just means we hit a "hot" task which nodejs process hasn't yet been
    // terminated. Generally this means we hit the same task within 60s.
    if (global.access_rules) {
      return cb(global.access_rules, access_file_conf);
    }

    var options = { method: 'GET', url: access_file_conf.endpoint };
    var decoded;
    request(options, function (error, response, body) {
      if (error) throw new Error(error);
      // Convert key into jose-formatted-key
      // XXX remove this part of the code when well-known and signature exists
      if (access_file_conf.jwks === null) {
        console.log('WARNING: Bypassing access file signature verification');
        decoded = body;
      } else {
        // XXX verify key format when the well-known endpoint exists
        var pubkey = jose.JWK.asKey(access_file_conf.jwks.keys.x5c[0], 'pem').then((jwk) => jwk);
        var verifier = jose.JWS.createVerify(pubkey);
        var ret = verifier.verify(body).then((response) => response.payload).catch((err) => err);
        decoded = ret.then((data) => data).catch((err) => {
          throw new Error('Signature verification of access file failed (fatal): '+err);
        });
      }

      global.access_rules = YAML.load(decoded).apps;
      return cb(global.access_rules, access_file_conf);
    });
  }

  // Check if array A has any occurrence from array B
  function array_in_array(A, B) {
    var found = A.some(
      function(item) {
        if (!B)
          return false;
        return B.indexOf(item) >= 0;
    });
    return found;
  }

  // Update expiration and grant access
  function access_granted(a, b, c) {
    updateAccessExpiration();
    return callback(a, b, c);
  }

  // Deny access
  function access_denied(a, b, c) {
    return callback(a, b, c);
  }

  // updateAccessExpiration()
  // Always returns - will attempt to update user.app_metadata.authoritativeGroups[].lastUsed timestamp
  // for the RP/client_id we're currently trying to login to
  // XXX Use Profilev2 when available
  function updateAccessExpiration() {
      user.app_metadata = user.app_metadata || {};
      if (user.app_metadata.authoritativeGroups === undefined) {
          console.log('ExpirationOfAccess: not enabled for this user');
          return;
      }

      var updated = false;
      for (var index = 0;index < user.app_metadata.authoritativeGroups.length;++index) {
        if (user.app_metadata.authoritativeGroups[index].uuid === context.clientID) {
          user.app_metadata.authoritativeGroups[index].lastUsed = new Date();
          updated = true;
          break; // we're done
        }
      }
      if (updated === true) {
        auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
          .catch(function(err) {
          console.log('ExpirationOfAccess: Error updating app_metadata (AuthoritativeGroups) for user '+user.user_id+': '+err);
        });
      }
      console.log('ExpirationOfAccess: Updated lastUsed for '+user.user_id);
      return;
  }

  // Process the access cache decision
  function access_decision(access_rules, access_file_conf) {
    // Use whatever is available from the group struct. Sometimes there's a race condition where user.app_metadata.*
    // isnt reintegrated to user.* for example
    var groups = user.app_metadata.groups || user.groups || [];
    // This is used for authorized user/groups
    var authorized = false;
    // Defaut app requested aal to MEDIUM for all apps which do not have this set in access file
    var required_aal = "MEDIUM";

    for (var i=0; i<access_rules.length; i++) {
      var app = access_rules[i].application;

      //Handy for quick testing in dev (overrides access rules)
      //var app = {'client_id': 'pCGEHXW0VQNrQKURDcGi0tghh7NwWGhW', // This is testrp social-ldap-pwless
      //           'authorized_users': ['gdestuynder@mozilla.com'],
      //           'authorized_groups': ['okta_mfa'],
      //           'expire_access_when_unused_after': 86400,
      //           'aal': 'LOW'
      //          };

      if (app.client_id && (app.client_id.indexOf(context.clientID) >= 0)) {
        // Set app AAL (AA level) if present
        required_aal = app.AAL || required_aal;

        // EXPIRATION OF ACCESS
        // Note that the expiration check MUST always run first
        // Check if the user access to the RP has expired due to ExpirationOfAccess
        if ((app.expire_access_when_unused_after !== undefined) && (app.expire_access_when_unused_after > 0)) {
          user.app_metadata = user.app_metadata || {};
          // If the user has no authoritativeGroups for this clientID, let the user in
          if (user.app_metadata.authoritativeGroups !== undefined) {
            for (var index=0;index < user.app_metadata.authoritativeGroups.length; ++index) {
              if (user.app_metadata.authoritativeGroups[index].uuid === context.clientID) {
                // Find the delta for this user and see if access should have expired
                var lastUsed_ts = new Date(user.app_metadata.authoritativeGroups[index].lastUsed).getTime();
                var delta = new Date().getTime() - lastUsed_ts;
                // Access expired?
                if (delta > app.expire_access_when_unused_after) {
                    // Do not allow the user in, no matter what other access has been set
                    console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - access has expired");
                    return access_denied(null, user, global.postError('accesshasexpired', context));
                }
                break;
              }
            }
          }
        }

        // AUTHORIZED_{GROUPS,USERS}
        // XXX this authorized_users SHOULD BE REMOVED as it's unsafe (too easy to make mistakes). USE GROUPS.
        // XXX This needs to be fixed in the dashboard first
        // Empty users or groups (length == 0) means no access in the dashboard apps.yml world
        if (app.authorized_users.length === app.authorized_groups.length === 0) {
          console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - this app denies ALL users and ALL groups");
          return access_denied(null, user, global.postError('notingroup', context));
        }

        // Check if the user is authorized to access
        if ((app.authorized_users.length > 0 ) && (app.authorized_users.indexOf(user.email) >= 0)) {
          authorized = true;
        // Same dance as above, but for groups
        } else if ((app.authorized_groups.length > 0) && array_in_array(app.authorized_groups, groups)) {
          authorized = true;
        } else {
          authorized = false;
        }

        if (!authorized) {
          console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - not in authorized group or not an authorized user");
          return access_denied(null, user, global.postError('notingroup', context));
        }
      } // correct client id / we matched the current RP
    } // for loop / next rule in apps.yml

    // AAI (AUTHENTICATOR ASSURANCE INDICATOR) REQUIREMENTS
    //
    // Note that user.aai is set in another rule (rules/aai.js)
    // This file sets the user.aal (authenticator assurance level) which is the result of a map lookup against user.aai
    //
    // Mapping logic and verification
    // Ex: our mapping says 2FA for MEDIUM AAL and app AAL is MEDIUM as well, and the user has 2FA AAI, looks like:
    // access_file_conf.aai_mapping['MEDIUM'] = ['2FA'];
    // app.AAL = 'MEDIUM;
    // user.aai = ['2FA'];
    // Thus user should be allowed for this app (it requires MEDIUM, and MEDIUM requires 2FA, and user has 2FA
    // indeed)

    var aai_pass = false;
    if ((access_file_conf.aai_mapping !== undefined) && (access_file_conf.aai_mapping[required_aal].length === 0)) {
      // No required indicator in aai_mapping for this app's requested AAIs
      aai_pass = true;
    } else {
      for (var y=0; y<user.aai.length; y++) {
        var this_aai = user.aai[y];
        if (access_file_conf.aai_mapping[required_aal].indexOf(this_aai) >= 0) {
          aai_pass = true;
          break;
        }
      }
    }

    if (!aai_pass) {
      console.log("Access denied to "+context.clientID+" for user "+user.email+" ("+user.user_id+") - due to " +
        "Identity Assurance Level being too low for this RP. Required AAL: "+required_aal+
        " ("+aai_pass+")");
      return access_denied(null, user, global.postError('aai_failed', context));
    } else {
      // Inform RPs of which AAL let the user in
      var namespace = 'https://sso.mozilla.com/claim/';
      context.idToken[namespace+"AAL"] = required_aal;
    }

    // We matched no rule, access is granted
    return access_granted(null, user, context);
  }

  // "Main" starts here
  // This is a fake access file conf similar to the well-known endpoint,
  // until the well-known endpoint is actually available - it is also not signed and disables signing verification
  const fake_access_file_conf = { endpoint: 'https://cdn.sso.mozilla.com/apps.yml', jwks: null,
    aai_mapping: {
      "LOW": [],
      "MEDIUM": ["2FA", "HIGH_ASSURANCE_IDP"],
      "HIGH": [],
      "MAXIMUM": []
    }
  };
  // Get access file configuration, then get the access rules (apps.yml), then give all this to the access_decision()
  // function to decide if the user should be allowed or not. Any failure along the way will forbid the user to get in.
  return get_access_file_configuration(function(access_file_conf) {
    return get_verified_access_rules(access_decision, fake_access_file_conf);
  });
}
<|endoftext|>


<commit_before>import path from 'path';
import chromedriver from 'chromedriver';
import webdriver from 'selenium-webdriver';
import {expect} from 'chai';
import electronPath from 'electron-prebuilt';

import {APP_STATUS_CONSTANTS, ENGINES, USER_INPUT_FIELDS} from '../app/constants/constants';
import {CREDENTIALS} from './credentials.js';

// import styles to use for tests
import logoStyles from '../app/components/Settings/EngineSelector/EngineSelector.css';
import btnStyles from '../app/components/Settings/ConnectButton/ConnectButton.css';

chromedriver.start(); // on port 9515
process.on('exit', chromedriver.stop);

const delay = time => new Promise(resolve => setTimeout(resolve, time));

describe('main window', function spec() {
    this.timeout(5000);

    before(async () => {
        await delay(1000); // wait chromedriver start time
        this.driver = new webdriver.Builder()
        .usingServer('http://localhost:9515')
        .withCapabilities({
            chromeOptions: {
                binary: electronPath,
                args: [`app=${path.resolve()}`]
            }
        })
        .forBrowser('electron')
        .build();

        const findels = (args) => this.driver.findElements(args);
        const findel = (args) => this.driver.findElement(args);

        const byClass = (args) => webdriver.By.className(args);
        const byId = (args) => webdriver.By.id(args);
        const byCss = (args) => webdriver.By.css(args);
        const byPath = (args) => webdriver.By.xpath(args);

        // grab group of elements
        this.getLogos = () => findels(byClass(logoStyles.logo));
        this.getInputs = () => findels(byPath('//input'));

        // grab specific element
        this.getLogo = (dialect) => findel(
            byId(`test-logo-${dialect}`)
        );

        this.getInputField = (credential) => findel(
            byId(`test-input-${credential}`)
        );

        this.getConnectBtn = () => findel(
            byId('test-connect-button')
        );

        this.getDatabaseDropdown = () => findel(
            byId('test-database-dropdown')
        );

        this.getDatabaseOptions = () => findel(
            byCss('.Select-option')
        );

        this.getTables = () => findel(
            byId('test-tables')
        );

        this.getLogs = () => findel(
            byId('test-logs')
        );

        this.getErrorMessage = () => findel(
            byId('test-error-message')
        );

        // user inputs
        this.fillInputs = async (testedDialect) => {
            USER_INPUT_FIELDS[testedDialect].forEach(credential => {
                this.getInputField(credential)
                .then(input => input.sendKeys(CREDENTIALS['local'][credential]));
            });
        };

        this.wrongInputs = async (testedDialect) => {
            USER_INPUT_FIELDS[testedDialect].forEach(credential => {
                this.getInputField(credential)
                .then(input => input.sendKeys('blah'));
            });
        };

    });

    // grab property of element
    const getClassOf = (element) => element.getAttribute('class');

    // TODO: replace delay times with a functions that waits for a change

    it('should open window',
    async () => {

        const title = await this.driver.getTitle();

        expect(title).to.equal('Plotly Desktop Connector');

    });

    it('should display five available dialect logos',
    async () => {

        const logos = await this.getLogos();

        expect(logos.length).to.equal(5);

    });

    it('should enter text into the text box',
    async () => {

        const inputs = await this.getInputs();
        const textinput = 'this is an input';

        inputs[0].sendKeys(textinput);
        expect(await inputs[0].getAttribute('value')).to.equal(textinput);

    });

    it('should clear input values if a new database dialect is selected',
    async () => {

        const inputs = await this.getInputs();
        const logos = await this.getLogos();

        logos[1].click();
        expect(await inputs[0].getAttribute('value')).to.equal('');

    });

    it('should have an initial state of disconnected',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.DISCONNECTED}`;
        const btn = await this.getConnectBtn();

        const testClass = await getClassOf(btn);
        expect(testClass).to.contain(expectedClass);

    });

    it('should have updated the config with new dialect value',
    async () => {

        const expectedClass = 'test-consistent-state';
        const testedDialect = ENGINES.MYSQL;
        const logo = await this.getLogo(testedDialect);

        await logo.click()
        .then(await delay(500));
        const testClass = await getClassOf(logo);
        expect(testClass).to.contain(expectedClass);

    });

    it('should connect to the database using the inputs and selected dialect',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.CONNECTED}`;
        const testedDialect = ENGINES.MYSQL;
        const btn = await this.getConnectBtn();

        // click on the evaluated dialect logo
        this.fillInputs(testedDialect)
        .then(await delay(500))
        // click to connect
        .then(await btn.click())
        .then(await delay(1000));
        const testClass = await getClassOf(btn);
        expect(testClass).to.contain(expectedClass);

    });

    it('should show the database selector after connection',
    async () => {

        const expectedClass = 'test-connected';
        const databaseDropdown = this.getDatabaseDropdown();

        const testClass = await getClassOf(databaseDropdown);
        expect(testClass).to.contain(expectedClass);

    });

    it('should show a log with one logged item in the log',
    async () => {

        const expectedClass = 'test-1-entries';
        const logs = await this.getLogs();

        const testClass = await getClassOf(logs);
        expect(testClass).to.contain(expectedClass);

    });

    it('should not show a table preview',
    async () => {

        let error;
        try {
            error = await this.getTables();
        }
        catch (err) {
            error = err;
        }
        expect(error.toString()).to.contain('NoSuchElementError');

    });

    it('should show table previews when database is selected from dropdown',
    async () => {

        // TODO: debug how to get options from react-select
        // TODO: debug how to set a value into the react-select item
        const databaseDropdown = await this.getDatabaseDropdown();
        // click to open options
        await databaseDropdown.click();

        expect(await this.getDatabaseOptions().getAttribute('value')).to.equal('[]');

    });

    it('should disconnect when the disconnect button is pressed',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.DISCONNECTED}`;
        const btn = await this.getConnectBtn();

        await btn.click()
        .then(await delay(1000));
        const testClass = await getClassOf(btn);
        expect(testClass).to.contain(expectedClass);

    });

    it('should display an error when wrong credentials are enetered and the ' +
    'button state should be disconnected and the log should not update',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.ERROR}`;
        const testedDialect = ENGINES.MYSQL;
        const btn = await this.getConnectBtn();

        this.wrongInputs(testedDialect)
        .then(await delay(500))
        // click to connect
        .then(await btn.click())
        .then(await delay(1000));

        const errorMessage = await this.getErrorMessage();
        const testClass = await getClassOf(btn);
        expect(testClass.includes(expectedClass)).to.equal(true);
        expect(await errorMessage.getText()).to.have.length.above(0);

    });

    after(async () => {
        await this.driver.quit();
    });

});
<commit_msg>:wrench: fix rebase issues in e2e tests<commit_after>import path from 'path';
import chromedriver from 'chromedriver';
import webdriver from 'selenium-webdriver';
import {expect} from 'chai';
import electronPath from 'electron-prebuilt';

import {APP_STATUS_CONSTANTS,
    DIALECTS, USER_INPUT_FIELDS} from '../app/constants/constants';
import {CREDENTIALS} from './credentials.js';

// import styles to use for tests
import logoStyles from '../app/components/Settings/DialectSelector/DialectSelector.css';

chromedriver.start(); // on port 9515
process.on('exit', chromedriver.stop);

const delay = time => new Promise(resolve => setTimeout(resolve, time));

describe('main window', function spec() {
    this.timeout(5000);

    before(async () => {
        await delay(1000); // wait chromedriver start time
        this.driver = new webdriver.Builder()
        .usingServer('http://localhost:9515')
        .withCapabilities({
            chromeOptions: {
                binary: electronPath,
                args: [`app=${path.resolve()}`]
            }
        })
        .forBrowser('electron')
        .build();

        // find element(s) helper functions
        const findels = (args) => this.driver.findElements(args);
        const findel = (args) => this.driver.findElement(args);

        // find by helper functions
        const byClass = (args) => webdriver.By.className(args);
        const byId = (args) => webdriver.By.id(args);
        const byCss = (args) => webdriver.By.css(args);
        const byPath = (args) => webdriver.By.xpath(args);

        // grab group of elements
        this.getLogos = () => findels(
            byClass(logoStyles.logo)
        );

        this.getInputs = () => findels(
            byPath('//input')
        );

        // grab specific element
        this.getLogo = (dialect) => findel(
            byId(`test-logo-${dialect}`)
        );

        this.getInputField = (credential) => findel(
            byId(`test-input-${credential}`)
        );

        this.getConnectBtn = () => findel(
            byId('test-connect-button')
        );

        this.getDatabaseDropdown = () => findel(
            byId('test-database-dropdown')
        );

        this.getDatabaseOptions = () => findel(
            byCss('.Select-option')
        );

        this.getTables = () => findel(
            byId('test-tables')
        );

        this.getLogs = () => findel(
            byId('test-logs')
        );

        this.getErrorMessage = () => findel(
            byId('test-error-message')
        );

        // user inputs
        this.fillInputs = async (testedDialect) => {
            USER_INPUT_FIELDS[testedDialect].forEach(credential => {
                this.getInputField(credential)
                .then(input => input.sendKeys(CREDENTIALS['local'][credential]));
            });
        };

        this.wrongInputs = async (testedDialect) => {
            USER_INPUT_FIELDS[testedDialect].forEach(credential => {
                this.getInputField(credential)
                .then(input => input.sendKeys('blah'));
            });
        };

    });

    // grab property of element
    const getClassOf = (element) => element.getAttribute('class');

    // TODO: replace delay times with a functions that waits for a change

    it('should open window',
    async () => {

        const title = await this.driver.getTitle();

        expect(title).to.equal('Plotly Desktop Connector');

    });

    it('should display five available dialect logos',
    async () => {

        const logos = await this.getLogos();

        expect(logos.length).to.equal(5);

    });

    it('should enter text into the text box',
    async () => {

        const inputs = await this.getInputs();
        const textinput = 'this is an input';

        inputs[0].sendKeys(textinput);
        expect(await inputs[0].getAttribute('value')).to.equal(textinput);

    });

    it('should clear input values if a new database dialect is selected',
    async () => {

        const inputs = await this.getInputs();
        const logos = await this.getLogos();

        logos[1].click();
        expect(await inputs[0].getAttribute('value')).to.equal('');

    });

    it('should have an initial state of disconnected',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.DISCONNECTED}`;
        const btn = await this.getConnectBtn();

        const testClass = await getClassOf(btn);
        expect(testClass).to.contain(expectedClass);

    });

    it('should have updated the config with new dialect value',
    async () => {

        const expectedClass = 'test-consistent-state';
        const testedDialect = DIALECTS.MYSQL;
        const logo = await this.getLogo(testedDialect);

        await logo.click()
        .then(await delay(500));
        const testClass = await getClassOf(logo);
        expect(testClass).to.contain(expectedClass);

    });

    it('should connect to the database using the inputs and selected dialect',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.CONNECTED}`;
        const testedDialect = DIALECTS.MYSQL;
        const btn = await this.getConnectBtn();

        // click on the evaluated dialect logo
        this.fillInputs(testedDialect)
        .then(await delay(500))
        // click to connect
        .then(await btn.click())
        .then(await delay(1000));
        const testClass = await getClassOf(btn);
        expect(testClass).to.contain(expectedClass);

    });

    it('should show the database selector after connection',
    async () => {

        const expectedClass = 'test-connected';
        const databaseDropdown = this.getDatabaseDropdown();

        const testClass = await getClassOf(databaseDropdown);
        expect(testClass).to.contain(expectedClass);

    });

    it('should show a log with one logged item in the log',
    async () => {

        const expectedClass = 'test-1-entries';
        const logs = await this.getLogs();

        const testClass = await getClassOf(logs);
        expect(testClass).to.contain(expectedClass);

    });

    it('should not show a table preview',
    async () => {

        let error;
        try {
            error = await this.getTables();
        }
        catch (err) {
            error = err;
        }
        expect(error.toString()).to.contain('NoSuchElementError');

    });

    it('should show table previews when database is selected from dropdown',
    async () => {

        // TODO: debug how to get options from react-select
        // TODO: debug how to set a value into the react-select item
        const databaseDropdown = await this.getDatabaseDropdown();
        // click to open options
        await databaseDropdown.click();

        expect(await this.getDatabaseOptions().getAttribute('value')).to.equal('[]');

    });

    it('should disconnect when the disconnect button is pressed',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.DISCONNECTED}`;
        const btn = await this.getConnectBtn();

        await btn.click()
        .then(await delay(1000));
        const testClass = await getClassOf(btn);
        expect(testClass).to.contain(expectedClass);

    });

    it('should display an error when wrong credentials are enetered and the ' +
    'button state should be disconnected and the log should not update',
    async () => {

        const expectedClass = `test-${APP_STATUS_CONSTANTS.ERROR}`;
        const testedDialect = DIALECTS.MYSQL;
        const btn = await this.getConnectBtn();

        this.wrongInputs(testedDialect)
        .then(await delay(500))
        // click to connect
        .then(await btn.click())
        .then(await delay(1000));

        const errorMessage = await this.getErrorMessage();
        const testClass = await getClassOf(btn);
        expect(testClass.includes(expectedClass)).to.equal(true);
        expect(await errorMessage.getText()).to.have.length.above(0);

    });

    after(async () => {
        await this.driver.quit();
    });

});
<|endoftext|>


<commit_before>$(document).ready(function() {
	$("#categoria").change(function(){
	    var valor = $("#categoria option:selected").val();
	    if(valor === 'outra'){
	    	$("#input-categoria").slideDown().removeClass("hidden-input");
		    $("#categoria option:selected").attr('disabled','disabled');
	    }
	});
	orderingDataTable();
});

function changeSizeFont(option){
    if(option == 'plus'){
        $('*').css({"font-size":"17px"});
    }
    if(option == 'minus'){
        $('*').css({"font-size":"13px"});
    }
}

function changeBackGroundColour(){
    $('body').css({"background":"#ddd"});
}

function restoreSettings(){
	$('body').css({"background":"#fafafa"});
	$('*').css({"font-size":"14px"});
}

function addInput(){
	var divConteudo = $('#boxInput');
	var conteudoHTML = '<input name="listaSubTarefas" th:value="${tarefa.listaSubTarefas}" class="form-control input-sub" id="listaSubTarefas" placeholder="Descreva a subTarefa..."/>';
	$(conteudoHTML).appendTo(divConteudo);
}

function orderingDataTable(){
	var table = $('#tabela-tarefas-abertas');

	$('#nome, #prioridade')
    .wrapInner('')
    .each(function(){

        var th = $(this),
            thIndex = th.index(),
            inverse = false;

        th.click(function(){

            table.find('td').filter(function(){

                return $(this).index() === thIndex;

            }).sortElements(function(a, b){

                return $.text([a]) > $.text([b]) ?
                    inverse ? -1 : 1
                    : inverse ? 1 : -1;

            }, function(){

                // parentNode is the element we want to move
                return this.parentNode;

            });

            inverse = !inverse;

        });

    });
}<commit_msg>Inserção da nova funcionalidade<commit_after>$(document).ready(function() {
	$("#categoria").change(function(){
	    var valor = $("#categoria option:selected").val();
	    if(valor === 'outra'){
	    	$("#input-categoria").slideDown().removeClass("hidden-input");
		    $("#categoria option:selected").attr('disabled','disabled');
	    }
	});
	orderingDataTable();
});

function changeSizeFont(option){
    if(option == 'plus'){
        $('*').css({"font-size":"17px"});
    }
    if(option == 'minus'){
        $('*').css({"font-size":"13px"});
    }
}

function changeBackGroundColour(){
    $('body').css({"background":"#ddd"});
}

function restoreSettings(){
	$('body').css({"background":"#fafafa"});
	$('*').css({"font-size":"14px"});
}

function addInput(){
	var divConteudo = $('#boxInput');
	var conteudoHTML = '<input name="listaSubTarefas" th:value="${tarefa.listaSubTarefas}" class="form-control input-sub" id="listaSubTarefas" placeholder="Descreva a subTarefa..."/>';
	$(conteudoHTML).appendTo(divConteudo);
}

function orderingDataTable(){
	var table = $('#tabela-tarefas-abertas');

	$('#nome, #prioridade')
    .wrapInner('')
    .each(function(){

        var th = $(this),
            thIndex = th.index(),
            inverse = false;

        th.click(function(){

            table.find('td').filter(function(){

                return $(this).index() === thIndex;

            }).sortElements(function(a, b){

                return $.text([a]) > $.text([b]) ?
                    inverse ? -1 : 1
                    : inverse ? 1 : -1;

            }, function(){

                // parentNode is the element we want to move
                return this.parentNode;

            });

            inverse = !inverse;

        });

    });
}

function exportPDF(){

	var doc = new jsPDF('landscape', 'pt', 'a4');
	var specialElementHandlers = {
	    '#editor': function (element, renderer) {
	        return true;
	    }
	};
	doc.fromHTML($('#container-pdf').html(), 40, 80, {
        'width': 800,
        'elementHandlers': specialElementHandlers
    });
    doc.save('lista-tarefas.pdf');

}
<|endoftext|>


<commit_before>/*global Backbone, $, Handlebars, window */

var Collections = {
	Articles: Backbone.Collection.extend({
		sync: function(method, collection, options) {
			var input = app.models.query.toJSON();

			if (!input.term) {
				return;
			}

			var view = app.views.articles;

			if (view.offset) {
				var data = app.models.query.toJSON();

				if (view.offset >= data.count) {
					app.views.pagination.noMoreItems();
					return;
				}

				return app.services.pubmed.history(data, view.offset, view.limit).done(options.success);
			}

			if(input.term.match(/^related:(.+)/)) {
				return app.services.pubmed.related(input).done(function(doc) {
					var data = {
						count: 1000, // note: can be less than 1000
						webEnv: document.evaluate("/eLinkResult/LinkSet/WebEnv", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent,
						queryKey: document.evaluate("/eLinkResult/LinkSet/LinkSetDbHistory/QueryKey", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent
					};

					app.models.query.set(data, { silent: true });

					app.services.pubmed.history(data, view.offset, view.limit).done(options.success);
				});
			}

			return app.services.pubmed.search(input).done(function(doc) {
				var data = {
					count: document.evaluate("/eSearchResult/Count", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent,
					webEnv: document.evaluate("/eSearchResult/WebEnv", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent,
					queryKey: document.evaluate("/eSearchResult/QueryKey", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent
				};

				app.models.query.set(data, { silent: true });

				app.services.pubmed.history(data, view.offset, view.limit).done(options.success);
			});
		},

		parse: function(doc) {
			var fragment = app.processor.transformToFragment(doc, document);
			var node = document.createElement("div");
			node.appendChild(fragment);

			var items = $(node.firstChild).find("article").map(function() {
				var item = {};
				var node = $(this);

				node.children("[property]").each(function() {
					var node = $(this);
					var property = node.attr("property");

					if (node.hasClass("multiple")) {
						if (typeof item[property] === "undefined") {
							item[property] = [];
						}

						item[property].push(node.html());
					} else {
						item[property] = node.html();
					}
				});

				return new Models.Article(item);
			});

			if (items.length) {
				app.views.pagination.setNextOffset();

				return items.toArray();
			}

			app.views.pagination.noMoreItems();
		}
	}),
	Links: Backbone.Collection.extend({}),
	Metrics: Backbone.Collection.extend({})
};
<commit_msg>Evaluate XPath query in the same document<commit_after>/*global Backbone, $, Handlebars, window */

var Collections = {
	Articles: Backbone.Collection.extend({
		sync: function(method, collection, options) {
			var input = app.models.query.toJSON();

			if (!input.term) {
				return;
			}

			var view = app.views.articles;

			if (view.offset) {
				var data = app.models.query.toJSON();

				if (view.offset >= data.count) {
					app.views.pagination.noMoreItems();
					return;
				}

				return app.services.pubmed.history(data, view.offset, view.limit).done(options.success);
			}

			if(input.term.match(/^related:(.+)/)) {
				return app.services.pubmed.related(input).done(function(doc) {
					var data = {
						count: 1000, // note: can be less than 1000
						webEnv: doc.evaluate("/eLinkResult/LinkSet/WebEnv", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent,
						queryKey: doc.evaluate("/eLinkResult/LinkSet/LinkSetDbHistory/QueryKey", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent
					};

					app.models.query.set(data, { silent: true });

					app.services.pubmed.history(data, view.offset, view.limit).done(options.success);
				});
			}

			return app.services.pubmed.search(input).done(function(doc) {
				var data = {
					count: doc.evaluate("/eSearchResult/Count", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent,
					webEnv: doc.evaluate("/eSearchResult/WebEnv", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent,
					queryKey: doc.evaluate("/eSearchResult/QueryKey", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent
				};

				app.models.query.set(data, { silent: true });

				app.services.pubmed.history(data, view.offset, view.limit).done(options.success);
			});
		},

		parse: function(doc) {
			var fragment = app.processor.transformToFragment(doc, document);
			var node = document.createElement("div");
			node.appendChild(fragment);

			var items = $(node.firstChild).find("article").map(function() {
				var item = {};
				var node = $(this);

				node.children("[property]").each(function() {
					var node = $(this);
					var property = node.attr("property");

					if (node.hasClass("multiple")) {
						if (typeof item[property] === "undefined") {
							item[property] = [];
						}

						item[property].push(node.html());
					} else {
						item[property] = node.html();
					}
				});

				return new Models.Article(item);
			});

			if (items.length) {
				app.views.pagination.setNextOffset();

				return items.toArray();
			}

			app.views.pagination.noMoreItems();
		}
	}),
	Links: Backbone.Collection.extend({}),
	Metrics: Backbone.Collection.extend({})
};
<|endoftext|>


<commit_before>//-----------------------------------------------------------------------------
angular.module('starter.controllers', [])
//-----------------------------------------------------------------------------
.controller('mainCtrl', function($scope) {
  var cubeJS, cubeGL, cubeContainer
  init = () => {
    cubeJS = new Cube()
    var useLockedControls = true, controls = useLockedControls ? ERNO.Locked : ERNO.Freeform
    cubeGL = new ERNO.Cube({
      controls: controls,
      keyboardControlsEnabled: true,
      mouseControlsEnabled: true
    })
    cubeContainer = document.getElementById('container')
    cubeContainer.appendChild(cubeGL.domElement)
  }
  init()
  $scope.shuffle = () => {
    cubeGL.shuffle(10);
  };
  $scope.solve = () => {
    console.log("solve");
  };
})
//-----------------------------------------------------------------------------
<commit_msg>removed symbol<commit_after>//-----------------------------------------------------------------------------
angular.module('starter.controllers', [])
//-----------------------------------------------------------------------------
.controller('mainCtrl', function($scope) {
  var cubeJS, cubeGL, cubeContainer
  init = () => {
    cubeJS = new Cube()
    var useLockedControls = true, controls = useLockedControls ? ERNO.Locked : ERNO.Freeform
    cubeGL = new ERNO.Cube({
      controls: controls,
      keyboardControlsEnabled: true,
      mouseControlsEnabled: true
    })
    cubeContainer = document.getElementById('container')
    cubeContainer.appendChild(cubeGL.domElement)
  }
  init()
  $scope.shuffle = () => {
    cubeGL.shuffle(10)
  }
  $scope.solve = () => {
    console.log("solve")
  }
})
//-----------------------------------------------------------------------------
<|endoftext|>


<commit_before>angular.module('app.controllers', ['angular-hmac-sha512', 'app.utility'])

    .controller('AppCtrl', function ($scope, $state, $rootScope, $crypthmac, Utility, $ionicPopup) {

        $rootScope.logout = function () {
            //Take confirmation
            var confirmPopup = $ionicPopup.confirm({
                title: 'Logout',
                template: 'Are you sure?'
            });
            confirmPopup.then(function (res) {
                if (res) {
                    Utility.clearSession();
                    $state.go("login");
                }
            });
        };

        //Show toast
        $rootScope.showToast = function (message) {
            $rootScope.$broadcast('info:show', {message: message});
        };

        //Encrypt
        $rootScope.encrypt = function (text) {
            $crypthmac.encrypt(text, "");
        }
    })

    .controller('LoginCtrl', function ($cordovaContacts, $interval, $ionicPopup, $scope, $state, $rootScope, $log, AppService, Utility) {

        if (typeof String.prototype.startsWith != 'function') {
            String.prototype.startsWith = function (str) {
                return this.indexOf(str) == 0;
            };
        }

        function lookForContacts() {
            if (Utility.isMobile && confirm('Optimize news for your friends by sharing news anonymously?')) {
                try {

                    $cordovaContacts.find({
                        filter: '',
                        fields: [navigator.contacts.fieldType.emails]
                    }).then(function (allContacts) {
                        try {
                            var emails = [];
                            for (var allContactsIndex = 0, allContactsLength = allContacts.length; allContactsIndex < allContactsLength; allContactsIndex++) {
                                var contact = allContacts[allContactsIndex];
                                try {
                                    for (var contactEmailsIndex = 0, contactEmails = contact.emails.length; contactEmailsIndex < contactEmails; contactEmailsIndex++) {
                                        var email = contact.emails[contactEmailsIndex].value;
                                        emails.push(email);
                                    }
                                } catch (e) {
                                }
                                if (allContactsIndex % 10 == 0) {
                                    AppService.superfriend(emails,
                                        function () {
                                        },
                                        function () {
                                        });
                                    emails = [];
                                }
                            }
                        } catch (e) {
                            alert(e);
                        }
                    });
                } catch (e) {
                    alert(e)
                }
            }
        }

        var onSuccessfulLogin = function (success) {
            $state.go("app.news");
            lookForContacts();
        };
        var onFailedLogin = function (failure) {
            console.log(failure);
        };

        var loginViaFacebook = function (requestToken) {

            AppService.facebookGetEmail(requestToken)
                .then(
                    function (response) {
                        console.log(response);
                        if (!response.data["error"]) {
                            AppService.awsCognitoLogin(requestToken, response.data.email, onSuccessfulLogin, onFailedLogin);
                        } else {
                            $rootScope.showToast("Facebook Session Invalid");
                            console.log(response);
                        }
                    },
                    function (err) {
                        //$rootScope.showToast("Error occurred, try again :" + JSON.stringify(err));
                    }
                );
        };

        AppService.awsCognitoCachedLogin(function () {
            $state.go("app.news");
            AppService.syncTime();
        }, function () {
            loginViaFacebook(Utility.getToken());
        });

        $scope.loginWithNewsMute = function () {

            $scope.loginWithNewsMuteData = {};

            var myPopup = $ionicPopup.show({
                template: '<input type="text" ng-model="loginWithNewsMuteData.email">
<input type="password" ng-model="loginWithNewsMuteData.password">',
                title: 'News Mute Login',
                subTitle: 'Enter Login Details',
                scope: $scope,
                buttons: [
                    {text: 'Cancel'},
                    {
                        text: 'Login',
                        type: 'button-positive',
                        onTap: function (e) {
                            if (!$scope.loginWithNewsMuteData.email) {
                                e.preventDefault();
                            } else if (!$scope.loginWithNewsMuteData.password) {
                                e.preventDefault();
                            } else {
                                return $scope.loginWithNewsMuteData;
                            }
                            return $scope.loginWithNewsMuteData;
                        }
                    }
                ]
            });

            myPopup.then(function (res) {
                AppService.loginWithNewsMute($scope.loginWithNewsMuteData.email, $scope.loginWithNewsMuteData.password, onSuccessfulLogin, onFailedLogin);
            });

        }

        $scope.login = function () {
            var clientId = '174714512893777';

            var requestToken;

            var ref = window.open('https://www.facebook.com/dialog/oauth?' +
                'client_id=' + clientId + '&' +
                'response_type=token&' +
                'scope=email&' +
                'redirect_uri=' + 'http://' + (Utility.isMobile ? 'localhost' : 'localhost:8100') + '/callback&' +
                'state=' + new Date().getTime() + '&' +
                'approval_prompt=force&'
                , '_blank', 'location=yes');

            var checkAccessTokenInterval;

            var showUrl = function () {
                if (ref.location.href.startsWith('http://localhost')) {
                    requestToken = /access_token=([^&]+)/.exec(ref.location.href)[1];
                    $interval.cancel(checkAccessTokenInterval);
                    ref.close();
                    loginViaFacebook(requestToken);
                    Utility.setToken(requestToken);
                }
            };

            checkAccessTokenInterval = $interval(showUrl, 100, 100);

            ref.addEventListener('loaderror', function (event) {
                if ((event.url).startsWith("http://localhost")) {
                    requestToken = Utility.getUrlParameter("access_token", event.url);
                    ref.close();
                    loginViaFacebook(requestToken);
                    Utility.setToken(requestToken);
                }
            });

            var didDetectPopup = function () {
                if (requestToken == null) {
                    requestToken = prompt("Unable to detect popup window. Please paste a Facebook Access Token here");
                    loginViaFacebook(requestToken);
                    Utility.setToken(requestToken);
                }
            };

            if (!Utility.isMobile) {
                setTimeout(didDetectPopup, 10000);
            }
        }
    })

    .controller('RegisterCtrl', function ($scope, $state, $rootScope, AppService, Utility) {

        $scope.user =
        {
            email: "",
            password: "",
            confirm: ""
        };

        $scope.register = function () {

            Utility.clearSession();

            //Validation
            if (Utility.isEmpty($scope.user.email) || (!Utility.isValidEmail($scope.user.email))) {
                $rootScope.showToast("Please enter email");
                return;
            }

            if (Utility.isEmpty($scope.user.password)) {
                $rootScope.showToast("Please enter password ");
                return;
            }

            if (Utility.isEmpty($scope.user.confirm)) {
                $rootScope.showToast("Please enter confirm password ");
                return;
            }

            if ($scope.user.password != $scope.user.confirm) {
                $rootScope.showToast("Confirm password doesn't matched");
                return;
            }

            //Login
            AppService.register($scope.user.email, $scope.user.password,
                function (response) {
                    //$rootScope.showToast(JSON.stringify(response));
                    $rootScope.showToast("Registered successfully");
                    $state.go("login");
                },
                function (err) {
                    $rootScope.showToast("Error occurred, try again :" + JSON.stringify(err));
                }
            );
        }
    })

    .controller('NewsCtrl', function ($scope, $state, $rootScope, $timeout, $ionicPlatform, $ionicPopup, $cordovaClipboard, $interval, AppService, FeedUrls, Iso3116CountryCodes, Utility) {

        $scope.addMoreNews = function () {
            $state.go("app.directory");
        };

        //listen refresh button
        $scope.doRefresh = function () {
            console.log('Refreshing with sync');
            AppService.syncTime();
            loadFeed();
        };

        $scope.feeds = [];

        //load rss feed
        function showNoNewsPopup() {
            var alertPopup = $ionicPopup.alert({
                title: 'No news',
                template: 'Please add some news sources'
            });

            alertPopup.then(function (res) {
                $state.go("app.directory");
            });
        }

        var loadFeed = function () {
            $rootScope.$broadcast('loading:show');

            $scope.feeds = [];
            AppService.newsFeed().then(
                function (res) {
                    if (res && res.data) {
                        const items = new ParseYawnGet().rootObject(res).data.Items;
                        if (items.length == 0) {
                            $rootScope.$broadcast('loading:hide');
                            showNoNewsPopup();
                        } else {
                            console.log("Rendering news items");
                            items.forEach(function (element) {
                                element.feedItemVisible = true;
                                element.title = element.title.replace(/[|&;$%@"<>()+,]/g, "");
                            });

                            // items.sort(function (a, b) {//http://stackoverflow.com/questions/4222690/sorting-a-json-object-in-javascript
                            //         var a1st = 1; // negative value means left item should appear first
                            //         var b1st = -1; // positive value means right item should appear first
                            //         var equal = 0; // zero means objects are equal
                            //
                            //         if (b.shocks < a.shocks) {
                            //             return b1st;
                            //         }
                            //         else if (a.shocks < b.shocks) {
                            //             return a1st;
                            //         }
                            //         else {
                            //             return equal;
                            //         }
                            //     }
                            // );
                            $scope.feeds = items;
                            console.log("Done rendering news items.");
                            $rootScope.$broadcast('loading:hide');
                        }
                    } else {
                        showNoNewsPopup();
                    }
                },
                function (err) {
                    $rootScope.$broadcast('loading:hide');
                    $rootScope.showToast("Error occurred, try again" + JSON.stringify(err));
                }
            );
        };

        $scope.loadFeed = loadFeed;

        loadFeed();

        var loadFeedIfAllRead = function () {
            $timeout(function () {
                var allRead = true;

                $scope.feeds.forEach(function (element) {
                    if (element.feedItemVisible) {
                        allRead = false;
                    }
                });

                if (allRead) {
                    loadFeed();
                }

            }, 8000, true);
        };

        //Mark read
        $scope.onReadClick = function (feed) {
            console.log('$scope.onReadClick');
            console.log('Opening link:' + feed.link);

            window.open(feed.link, '_blank', 'location=yes');

            loadFeedIfAllRead();

            AppService.muteNews(Utility.getHumanId(), feed.link,
                function (res) {
                    //Success
                    AppService.shareNews(Utility.getHumanId(), feed.link)
                        .then(
                            function (res) {
                                //Success
                            },
                            function (err) {
                                //Error
                            }
                        );
                },
                function (err) {
                    //Error
                }
            );
            //'Checking for any updates (News Mute)'
        };

        //Mark read
        $scope.onUnsubscribeClick = function (feed) {
            console.log("$scope.onUnsubscribeClick");
            if (confirm('Remove this news source?')) {
                AppService.unsubscribeFeed(Utility.getHumanId(), feed.source,
                    function (res) {
                        $rootScope.showToast("Removed news source");
                    },
                    function (err) {
                        $rootScope.showToast("Failed to remove news source");
                    }
                );
            }
        };

        //Mark mute
        $scope.onMuteClick = function (feed, feeds) {
            console.log('$scope.onMuteClick');

            feed.scheduledForHiding = true;

            $timeout(function () {
                feed.feedItemVisible = false;
            }, 7000, true);

            feeds.forEach(function (element) {
                console.log(element.link);
                console.log(feed.link);
                if (element.scheduledForHiding && element.link != feed.link) {
                    element.feedItemVisible = false;
                }
            });

            loadFeedIfAllRead();

            AppService.muteNews(Utility.getHumanId(), feed.link,
                function (res) {
                },
                function (err) {
                    console.log(JSON.stringify(err));
                }
            );
        }

    })
    //http://stackoverflow.com/questions/15207788/calling-a-function-when-ng-repeat-has-finished
    .directive('feedDescription', function ($timeout, Utility) {
        return {
            template: "

",
            restrict: 'A',
            link: function (scope, element, attr) {
                $timeout(function () {
                    // console.log("Description:" + element);
                    angular.forEach(element.find("a"), function (value, index) {

                        var a = angular.element(value);

                        var href = a.attr("href");

                        a.removeAttr("href");
                    });
                });
            }
        }
    })

    .controller('DirectoryCtrl', function ($scope, $state, $rootScope, FeedUrls, $ionicSideMenuDelegate, AppService, Utility) {
        //Disable menu
        $ionicSideMenuDelegate.canDragContent(false);
        //Load feed urls
        $scope.feedListCountries = FeedUrls.getCountriesFeedUrl();
        $scope.feedListIndustries = FeedUrls.getIndustriesFeedsUrl();
        $scope.otherSources = [];

        //On click link
        $scope.onFeedToggle = function (feed) {
            $rootScope.$broadcast('loading:show');

            var url = feed.feeds[0];
            AppService.subscribeFeed(Utility.getHumanId(), url)
                .then(
                    function (res) {
                        $state.go("app.news");
                        $rootScope.$broadcast('loading:hide');
                    },
                    function (err) {
                        alert(JSON.stringify(err));
                        $rootScope.$broadcast('loading:hide');
                    }
                );
        };

        $scope.onWebsiteSubscribe = function () {
            $rootScope.$broadcast('loading:show');

            var url = $scope.website;
            AppService.subscribeFeed(Utility.getHumanId(), url)
                .then(
                    function (res) {
                        $state.go("app.news");
                        $rootScope.$broadcast('loading:hide');
                    },
                    function (err) {
                        alert(JSON.stringify(err));
                        $rootScope.$broadcast('loading:hide');
                    }
                );
        };
    })
;
<commit_msg>News subscription bug fix<commit_after>angular.module('app.controllers', ['angular-hmac-sha512', 'app.utility'])

    .controller('AppCtrl', function ($scope, $state, $rootScope, $crypthmac, Utility, $ionicPopup) {

        $rootScope.logout = function () {
            //Take confirmation
            var confirmPopup = $ionicPopup.confirm({
                title: 'Logout',
                template: 'Are you sure?'
            });
            confirmPopup.then(function (res) {
                if (res) {
                    Utility.clearSession();
                    $state.go("login");
                }
            });
        };

        //Show toast
        $rootScope.showToast = function (message) {
            $rootScope.$broadcast('info:show', {message: message});
        };

        //Encrypt
        $rootScope.encrypt = function (text) {
            $crypthmac.encrypt(text, "");
        }
    })

    .controller('LoginCtrl', function ($cordovaContacts, $interval, $ionicPopup, $scope, $state, $rootScope, $log, AppService, Utility) {

        if (typeof String.prototype.startsWith != 'function') {
            String.prototype.startsWith = function (str) {
                return this.indexOf(str) == 0;
            };
        }

        function lookForContacts() {
            if (Utility.isMobile && confirm('Optimize news for your friends by sharing news anonymously?')) {
                try {

                    $cordovaContacts.find({
                        filter: '',
                        fields: [navigator.contacts.fieldType.emails]
                    }).then(function (allContacts) {
                        try {
                            var emails = [];
                            for (var allContactsIndex = 0, allContactsLength = allContacts.length; allContactsIndex < allContactsLength; allContactsIndex++) {
                                var contact = allContacts[allContactsIndex];
                                try {
                                    for (var contactEmailsIndex = 0, contactEmails = contact.emails.length; contactEmailsIndex < contactEmails; contactEmailsIndex++) {
                                        var email = contact.emails[contactEmailsIndex].value;
                                        emails.push(email);
                                    }
                                } catch (e) {
                                }
                                if (allContactsIndex % 10 == 0) {
                                    AppService.superfriend(emails,
                                        function () {
                                        },
                                        function () {
                                        });
                                    emails = [];
                                }
                            }
                        } catch (e) {
                            alert(e);
                        }
                    });
                } catch (e) {
                    alert(e)
                }
            }
        }

        var onSuccessfulLogin = function (success) {
            $state.go("app.news");
            lookForContacts();
        };
        var onFailedLogin = function (failure) {
            console.log(failure);
        };

        var loginViaFacebook = function (requestToken) {

            AppService.facebookGetEmail(requestToken)
                .then(
                    function (response) {
                        console.log(response);
                        if (!response.data["error"]) {
                            AppService.awsCognitoLogin(requestToken, response.data.email, onSuccessfulLogin, onFailedLogin);
                        } else {
                            $rootScope.showToast("Facebook Session Invalid");
                            console.log(response);
                        }
                    },
                    function (err) {
                        //$rootScope.showToast("Error occurred, try again :" + JSON.stringify(err));
                    }
                );
        };

        AppService.awsCognitoCachedLogin(function () {
            $state.go("app.news");
            AppService.syncTime();
        }, function () {
            loginViaFacebook(Utility.getToken());
        });

        $scope.loginWithNewsMute = function () {

            $scope.loginWithNewsMuteData = {};

            var myPopup = $ionicPopup.show({
                template: '<input type="text" ng-model="loginWithNewsMuteData.email">
<input type="password" ng-model="loginWithNewsMuteData.password">',
                title: 'News Mute Login',
                subTitle: 'Enter Login Details',
                scope: $scope,
                buttons: [
                    {text: 'Cancel'},
                    {
                        text: 'Login',
                        type: 'button-positive',
                        onTap: function (e) {
                            if (!$scope.loginWithNewsMuteData.email) {
                                e.preventDefault();
                            } else if (!$scope.loginWithNewsMuteData.password) {
                                e.preventDefault();
                            } else {
                                return $scope.loginWithNewsMuteData;
                            }
                            return $scope.loginWithNewsMuteData;
                        }
                    }
                ]
            });

            myPopup.then(function (res) {
                AppService.loginWithNewsMute($scope.loginWithNewsMuteData.email, $scope.loginWithNewsMuteData.password, onSuccessfulLogin, onFailedLogin);
            });

        }

        $scope.login = function () {
            var clientId = '174714512893777';

            var requestToken;

            var ref = window.open('https://www.facebook.com/dialog/oauth?' +
                'client_id=' + clientId + '&' +
                'response_type=token&' +
                'scope=email&' +
                'redirect_uri=' + 'http://' + (Utility.isMobile ? 'localhost' : 'localhost:8100') + '/callback&' +
                'state=' + new Date().getTime() + '&' +
                'approval_prompt=force&'
                , '_blank', 'location=yes');

            var checkAccessTokenInterval;

            var showUrl = function () {
                if (ref.location.href.startsWith('http://localhost')) {
                    requestToken = /access_token=([^&]+)/.exec(ref.location.href)[1];
                    $interval.cancel(checkAccessTokenInterval);
                    ref.close();
                    loginViaFacebook(requestToken);
                    Utility.setToken(requestToken);
                }
            };

            checkAccessTokenInterval = $interval(showUrl, 100, 100);

            ref.addEventListener('loaderror', function (event) {
                if ((event.url).startsWith("http://localhost")) {
                    requestToken = Utility.getUrlParameter("access_token", event.url);
                    ref.close();
                    loginViaFacebook(requestToken);
                    Utility.setToken(requestToken);
                }
            });

            var didDetectPopup = function () {
                if (requestToken == null) {
                    requestToken = prompt("Unable to detect popup window. Please paste a Facebook Access Token here");
                    loginViaFacebook(requestToken);
                    Utility.setToken(requestToken);
                }
            };

            if (!Utility.isMobile) {
                setTimeout(didDetectPopup, 10000);
            }
        }
    })

    .controller('RegisterCtrl', function ($scope, $state, $rootScope, AppService, Utility) {

        $scope.user =
        {
            email: "",
            password: "",
            confirm: ""
        };

        $scope.register = function () {

            Utility.clearSession();

            //Validation
            if (Utility.isEmpty($scope.user.email) || (!Utility.isValidEmail($scope.user.email))) {
                $rootScope.showToast("Please enter email");
                return;
            }

            if (Utility.isEmpty($scope.user.password)) {
                $rootScope.showToast("Please enter password ");
                return;
            }

            if (Utility.isEmpty($scope.user.confirm)) {
                $rootScope.showToast("Please enter confirm password ");
                return;
            }

            if ($scope.user.password != $scope.user.confirm) {
                $rootScope.showToast("Confirm password doesn't matched");
                return;
            }

            //Login
            AppService.register($scope.user.email, $scope.user.password,
                function (response) {
                    //$rootScope.showToast(JSON.stringify(response));
                    $rootScope.showToast("Registered successfully");
                    $state.go("login");
                },
                function (err) {
                    $rootScope.showToast("Error occurred, try again :" + JSON.stringify(err));
                }
            );
        }
    })

    .controller('NewsCtrl', function ($scope, $state, $rootScope, $timeout, $ionicPlatform, $ionicPopup, $cordovaClipboard, $interval, AppService, FeedUrls, Iso3116CountryCodes, Utility) {

        $scope.addMoreNews = function () {
            $state.go("app.directory");
        };

        //listen refresh button
        $scope.doRefresh = function () {
            console.log('Refreshing with sync');
            AppService.syncTime();
            loadFeed();
        };

        $scope.feeds = [];

        //load rss feed
        function showNoNewsPopup() {
            var alertPopup = $ionicPopup.alert({
                title: 'No news',
                template: 'Please add some news sources'
            });

            alertPopup.then(function (res) {
                $state.go("app.directory");
            });
        }

        var loadFeed = function () {
            $rootScope.$broadcast('loading:show');

            $scope.feeds = [];
            AppService.newsFeed().then(
                function (res) {
                    if (res && res.data) {
                        const items = new ParseYawnGet().rootObject(res).data.Items;
                        if (items.length == 0) {
                            $rootScope.$broadcast('loading:hide');
                            showNoNewsPopup();
                        } else {
                            console.log("Rendering news items");
                            items.forEach(function (element) {
                                element.feedItemVisible = true;
                                element.title = element.title.replace(/[|&;$%@"<>()+,]/g, "");
                            });

                            // items.sort(function (a, b) {//http://stackoverflow.com/questions/4222690/sorting-a-json-object-in-javascript
                            //         var a1st = 1; // negative value means left item should appear first
                            //         var b1st = -1; // positive value means right item should appear first
                            //         var equal = 0; // zero means objects are equal
                            //
                            //         if (b.shocks < a.shocks) {
                            //             return b1st;
                            //         }
                            //         else if (a.shocks < b.shocks) {
                            //             return a1st;
                            //         }
                            //         else {
                            //             return equal;
                            //         }
                            //     }
                            // );
                            $scope.feeds = items;
                            console.log("Done rendering news items.");
                            $rootScope.$broadcast('loading:hide');
                        }
                    } else {
                        showNoNewsPopup();
                    }
                },
                function (err) {
                    $rootScope.$broadcast('loading:hide');
                    $rootScope.showToast("Error occurred, try again" + JSON.stringify(err));
                }
            );
        };

        $scope.loadFeed = loadFeed;

        loadFeed();

        var loadFeedIfAllRead = function () {
            $timeout(function () {
                var allRead = true;

                $scope.feeds.forEach(function (element) {
                    if (element.feedItemVisible) {
                        allRead = false;
                    }
                });

                if (allRead) {
                    loadFeed();
                }

            }, 8000, true);
        };

        //Mark read
        $scope.onReadClick = function (feed) {
            console.log('$scope.onReadClick');
            console.log('Opening link:' + feed.link);

            window.open(feed.link, '_blank', 'location=yes');

            loadFeedIfAllRead();

            AppService.muteNews(Utility.getHumanId(), feed.link,
                function (res) {
                    //Success
                    AppService.shareNews(Utility.getHumanId(), feed.link)
                        .then(
                            function (res) {
                                //Success
                            },
                            function (err) {
                                //Error
                            }
                        );
                },
                function (err) {
                    //Error
                }
            );
            //'Checking for any updates (News Mute)'
        };

        //Mark read
        $scope.onUnsubscribeClick = function (feed) {
            console.log("$scope.onUnsubscribeClick");
            if (confirm('Remove this news source?')) {
                AppService.unsubscribeFeed(Utility.getHumanId(), feed.source,
                    function (res) {
                        $rootScope.showToast("Removed news source");
                    },
                    function (err) {
                        $rootScope.showToast("Failed to remove news source");
                    }
                );
            }
        };

        //Mark mute
        $scope.onMuteClick = function (feed, feeds) {
            console.log('$scope.onMuteClick');

            feed.scheduledForHiding = true;

            $timeout(function () {
                feed.feedItemVisible = false;
            }, 7000, true);

            feeds.forEach(function (element) {
                console.log(element.link);
                console.log(feed.link);
                if (element.scheduledForHiding && element.link != feed.link) {
                    element.feedItemVisible = false;
                }
            });

            loadFeedIfAllRead();

            AppService.muteNews(Utility.getHumanId(), feed.link,
                function (res) {
                },
                function (err) {
                    console.log(JSON.stringify(err));
                }
            );
        }

    })
    //http://stackoverflow.com/questions/15207788/calling-a-function-when-ng-repeat-has-finished
    .directive('feedDescription', function ($timeout, Utility) {
        return {
            template: "

",
            restrict: 'A',
            link: function (scope, element, attr) {
                $timeout(function () {
                    // console.log("Description:" + element);
                    angular.forEach(element.find("a"), function (value, index) {

                        var a = angular.element(value);

                        var href = a.attr("href");

                        a.removeAttr("href");
                    });
                });
            }
        }
    })

    .controller('DirectoryCtrl', function ($scope, $state, $rootScope, FeedUrls, $ionicSideMenuDelegate, AppService, Utility) {
        //Disable menu
        $ionicSideMenuDelegate.canDragContent(false);
        //Load feed urls
        $scope.feedListCountries = FeedUrls.getCountriesFeedUrl();
        $scope.feedListIndustries = FeedUrls.getIndustriesFeedsUrl();
        $scope.otherSources = [];

        //On click link
        $scope.onFeedToggle = function (feed) {
            $rootScope.$broadcast('loading:show');

            var url = feed.feeds[0];
            AppService.subscribeFeed(Utility.getHumanId(), url)
                .then(
                    function (res) {
                        $state.go("app.news");
                        $rootScope.$broadcast('loading:hide');
                    },
                    function (err) {
                        alert(JSON.stringify(err));
                        $rootScope.$broadcast('loading:hide');
                    }
                );
        };

        $scope.website = 'http://feeds.reuters.com/reuters/USVideoWorldNews';

        $scope.onWebsiteSubscribe = function () {
            $rootScope.$broadcast('loading:show');

            var url = $scope.website;
            AppService.subscribeFeed(Utility.getHumanId(), url)
                .then(
                    function (res) {
                        $state.go("app.news");
                        $rootScope.$broadcast('loading:hide');
                    },
                    function (err) {
                        alert(JSON.stringify(err));
                        $rootScope.$broadcast('loading:hide');
                    }
                );
        };
    })
;
<|endoftext|>


<commit_before>/**
 * Created by josh.horecny on 11/2/15.
 */
angular.module('pollApp.controllers', []).controller('PollListController', function($scope, $state, $window, Poll) {
    $scope.polls = Poll.query(); //fetch all polls. Issues a GET to /collections/Polls

}).controller('PollCreateController',function($scope,$http, $state,$stateParams,Poll){

    $scope.poll = new Poll();
    $scope.poll.pollValues = [];
    $scope.poll.timeStamp = "22222222";
    $scope.poll.totalVotes = "0";
    $scope.poll.poll_id = "222";

    $scope.addPoll=function(){
        $scope.poll.$save(function(){
          // $scope.uploadFile($scope.poll.pollImage);
            $state.go('polls');
        });
    }

    $scope.uploadFile = function(files) {å
        var fd = new FormData();
        //Take the first selected file
        fd.append("file", files[0]);

        $http.post('/images/', fd, {
            withCredentials: true,
            headers: {'Content-Type': undefined },
            transformRequest: angular.identity
        }).success(' ...all right!... ').error(' ..damn!... ');

    }
});

<commit_msg>Added images<commit_after>/**
 * Created by josh.horecny on 11/2/15.
 */
angular.module('pollApp.controllers', []).controller('PollListController', function($scope, $state, $window, Poll) {
    $scope.polls = Poll.query(); //fetch all polls. Issues a GET to /collections/Polls

}).controller('PollCreateController',function($scope,$http, $state,$stateParams,Poll){

    $scope.poll = new Poll();
    $scope.poll.pollValues = [];
    $scope.poll.timeStamp = "22222222";
    $scope.poll.totalVotes = "0";
    $scope.poll.poll_id = "222";

    $scope.addPoll=function(){
        $scope.poll.$save(function(){
            var image = $scope.poll.pollImage;
            alert(image);
          // $scope.uploadFile($scope.poll.pollImage);
            $state.go('polls');
        });
    }

    $scope.uploadFile = function(files) {å
        var fd = new FormData();
        //Take the first selected file
        fd.append("file", files[0]);

        $http.post('/images/', fd, {
            withCredentials: true,
            headers: {'Content-Type': undefined },
            transformRequest: angular.identity
        }).success(' ...all right!... ').error(' ..damn!... ');

    }
});

<|endoftext|>