<commit_before>import message from 'focus-core/message';
import { changeMode } from 'focus-core/application';
import reduce from 'lodash/collection/reduce';

const changeBehaviourMixin = {
    /**
    * Display a message when there is a change on a store property resulting from a component action call.
    * @param  {object} changeInfos - An object containing all the event informations, without the data.
    * @return {function} - An override function can be called.
    */
    _displayMessageOnChange: function displayMessageOnChange(changeInfos) {
        if (this.displayMessageOnChange) {
            return this.displayMessageOnChange(changeInfos);
        }
        if (changeInfos && changeInfos.status && changeInfos.status.name) {
            switch (changeInfos.status.name) {
                /* case 'loading':
                Focus.message.addInformationMessage('detail.loading');
                break;
                case 'loaded':
                Focus.message.addSuccessMessage('detail.loaded');
                break;
                case 'saving':
                Focus.message.addInformationMessage('detail.saving');
                break;*/
                case 'saved':
                    //Maybe the action result or the event should have a caller notion.
                    message.addSuccessMessage('detail.saved');
                    //Change the page mode as edit
                    this.setState({ isEdit: false }, () => {
                        changeMode('consult', 'edit');
                    });
                    break;
                default:
                    break;
            }
        }
    },
    /**
    * After change informations.
    * You can override this method using afterChange function.
    * @param {object} changeInfos - All informations relative to the change.
    * @returns {undefined} -  The return value is the callback.
    */
    _afterChange: function afterChangeForm(changeInfos) {
        if (this.afterChange) {
            return this.afterChange(changeInfos);
        }
        //If there is no callerId in the event, the display message does not have any sens.
        //Other component responding to the store property change does not need to react on it.
        if (changeInfos && changeInfos.informations && changeInfos.informations.callerId && this._identifier === changeInfos.informations.callerId) {
            return this._displayMessageOnChange(changeInfos);
        }

    },
    /**
    * Event handler for 'change' events coming from the stores
    * @param {object} changeInfos - The changing informations.
    */
    _onChange: function onFormStoreChangeHandler(changeInfos) {
        let onChange = this.props.onChange || this.onChange;
        if (onChange) {
            onChange.call(this, changeInfos);
        }
        this.setState(this._getStateFromStores(changeInfos.property), () => this._afterChange(changeInfos));
    },
    /**
    * Event handler for 'error' events coming from the stores.
    */
    _onError: function onFormErrorHandler(changeInfos) {
        this.setState(this._getLoadingStateFromStores(), () => this._handleErrors(changeInfos)); // update errors after status
    },
    _handleErrors() {
        const errorState = this._getErrorStateFromStores();
        if (this.definitionPath) {
            // In case we have a definitionPath, we might want to trigger a setError on the corresponding field
            for (let key in errorState) {
                // Let's find that corresponding field, considering that the ref might not directly be 'storeNode.fieldName', but in fact 'entityPath.fieldName'
                const ref = reduce(this.refs, (acc, value, candidateRef) => {
                    const candidate = candidateRef.replace(`${this.definitionPath}.`, ''); // Remove the 'definitionPath.'
                    if (candidate === key.match(/([^\.]*)$/)[0]) { // Look for the 'fieldName' part of 'storeNode.fieldName'
                        acc = value;
                    }
                    return acc;
                }, null);
                if (ref) { // If we found it, then bingo
                    ref.setError(errorState[key]);
                }
            }
        }
    },
    /**
    * Read
    * @param  {[type]} changeInfos [description]
    * @return {[type]}             [description]
    */
    _onStatus(changeInfos) {
        if (this._getEntity) {
            this.setState({ ...this._getEntity(), ...this._getLoadingStateFromStores() });
        } else {
            this.setState(this._getLoadingStateFromStores());
        }

    }
};
module.exports = changeBehaviourMixin;
<commit_msg>Making store-change-behaviour safer : events will be buffered if the component is not mounted<commit_after>import message from 'focus-core/message';
import { changeMode } from 'focus-core/application';
import reduce from 'lodash/collection/reduce';

const changeBehaviourMixin = {
    getInitialState: function getInitialState() {
        return {};
    },
    componentWillMount() {
        this._isMountedChangeBehaviourMixin = false;
        this._pendingActionsChangeBehaviourMixin = [];
    },
    componentDidMount() {
        this._isMountedChangeBehaviourMixin = true;
        this._pendingActionsChangeBehaviourMixin.forEach(func => func());
        this._pendingActionsChangeBehaviourMixin = [];
    },
    componentWillUnmount() {
        this._isMountedChangeBehaviourMixin = false;
    },

    /**
    * Display a message when there is a change on a store property resulting from a component action call.
    * @param  {object} changeInfos - An object containing all the event informations, without the data.
    * @return {function} - An override function can be called.
    */
    _displayMessageOnChange: function displayMessageOnChange(changeInfos) {
        if (this.displayMessageOnChange) {
            return this.displayMessageOnChange(changeInfos);
        }
        if (changeInfos && changeInfos.status && changeInfos.status.name) {
            switch (changeInfos.status.name) {
                /* case 'loading':
                Focus.message.addInformationMessage('detail.loading');
                break;
                case 'loaded':
                Focus.message.addSuccessMessage('detail.loaded');
                break;
                case 'saving':
                Focus.message.addInformationMessage('detail.saving');
                break;*/
                case 'saved':
                    //Maybe the action result or the event should have a caller notion.
                    message.addSuccessMessage('detail.saved');
                    //Change the page mode as edit
                    this.setState({ isEdit: false }, () => {
                        changeMode('consult', 'edit');
                    });
                    break;
                default:
                    break;
            }
        }
    },
    _afterChange: function afterChangeWrapper(changeInfos) {
        if (this._isMountedChangeBehaviourMixin) {
            this._afterChangeWrapped(changeInfos);
        } else {
            this._pendingActionsChangeBehaviourMixin.push(() => this._afterChangeWrapped(changeInfos));
        }
    },
    /**
    * After change informations.
    * You can override this method using afterChange function.
    * @param {object} changeInfos - All informations relative to the change.
    * @returns {undefined} -  The return value is the callback.
    */
    _afterChangeWrapped: function afterChangeFormWrapped(changeInfos) {
        if (this.afterChange) {
            return this.afterChange(changeInfos);
        }
        //If there is no callerId in the event, the display message does not have any sens.
        //Other component responding to the store property change does not need to react on it.
        if (changeInfos && changeInfos.informations && changeInfos.informations.callerId && this._identifier === changeInfos.informations.callerId) {
            return this._displayMessageOnChange(changeInfos);
        }

    },
    _onChange: function _onChangeWrapper(changeInfos) {
        if (this._isMountedChangeBehaviourMixin) {
            this._onChangeWrapped(changeInfos);
        } else {
            this._pendingActionsChangeBehaviourMixin.push(() => this._onChangeWrapped(changeInfos));
        }
    },
    /**
    * Event handler for 'change' events coming from the stores
    * @param {object} changeInfos - The changing informations.
    */
    _onChangeWrapped: function onFormStoreChangeHandler(changeInfos) {
        let onChange = this.props.onChange || this.onChange;
        if (onChange) {
            onChange.call(this, changeInfos);
        }
        this.setState(this._getStateFromStores(changeInfos.property), () => this._afterChange(changeInfos));
    },
    _onError: function _onErrorWrapper(changeInfos) {
        if (this._isMountedChangeBehaviourMixin) {
            this._onErrorWrapped(changeInfos);
        } else {
            this._pendingActionsChangeBehaviourMixin.push(() => this._onErrorWrapped(changeInfos));
        }
    },
    /**
    * Event handler for 'error' events coming from the stores.
    */
    _onErrorWrapped: function onFormErrorHandler(changeInfos) {
        this.setState(this._getLoadingStateFromStores(), () => this._handleErrors(changeInfos)); // update errors after status
    },
    _handleErrors() {
        const errorState = this._getErrorStateFromStores();
        if (this.definitionPath) {
            // In case we have a definitionPath, we might want to trigger a setError on the corresponding field
            for (let key in errorState) {
                // Let's find that corresponding field, considering that the ref might not directly be 'storeNode.fieldName', but in fact 'entityPath.fieldName'
                const ref = reduce(this.refs, (acc, value, candidateRef) => {
                    const candidate = candidateRef.replace(`${this.definitionPath}.`, ''); // Remove the 'definitionPath.'
                    if (candidate === key.match(/([^\.]*)$/)[0]) { // Look for the 'fieldName' part of 'storeNode.fieldName'
                        acc = value;
                    }
                    return acc;
                }, null);
                if (ref) { // If we found it, then bingo
                    ref.setError(errorState[key]);
                }
            }
        }
    },
    _onStatus: function _onStatusWrapper(changeInfos) {
        if (this._isMountedChangeBehaviourMixin) {
            this._onStatusWrapped(changeInfos);
        } else {
            this._pendingActionsChangeBehaviourMixin.push(() => this._onStatusWrapped(changeInfos));
        }
    },
    /**
    * Read
    * @param  {[type]} changeInfos [description]
    * @return {[type]}             [description]
    */
    _onStatusWrapped: function _onStatus(changeInfos) {
        if (this._getEntity) {
            this.setState({ ...this._getEntity(), ...this._getLoadingStateFromStores() });
        } else {
            this.setState(this._getLoadingStateFromStores());
        }

    }
};
module.exports = changeBehaviourMixin;
<|endoftext|>


<commit_before>/* eslint-disable react/no-danger */
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
// import { inject } from 'mobx-react';
import { compose, getContext, mapProps } from 'recompose';
import { decode } from 'he';
import GoogleTagManager from './GoogleTagManager';
import GoogleAnalytics from './GoogleAnalytics';
import ComScore from './ComScore';

const Analytics = ({
  gtmIds,
  gtmClientProperties,
  gtmPageViewProperties,
  gaIds,
  gaCustomDimensions,
  comScoreIds,
  title,
}) => {
  const { title: _, ...gtmPageView } = gtmPageViewProperties;
  return (
    <Fragment>
      <script
        dangerouslySetInnerHTML={{
          __html: `
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  'gtm.start': Date.now(),
  event: 'gtm.js',
});
window.dataLayer.push({
  event: 'wpPwaProperties',
  wpPwaProperties: ${JSON.stringify(gtmClientProperties)},
});
window.dataLayer.push({
  event: 'virtualPageview',
  virtualPageview: ${JSON.stringify({ title, ...gtmPageView })},
});`,
        }}
      />
      {/* <GoogleTagManager key="GTM-K3S2BMT" id="GTM-K3S2BMT" /> */}
      {gtmIds.map(id => <GoogleTagManager key={id} id={id} />)}
      {gaIds.map(id => (
        <GoogleAnalytics
          key={id}
          id={id}
          customDimensions={gaCustomDimensions}
        />
      ))}
      {comScoreIds.map(id => <ComScore key={id} id={id} />)}
    </Fragment>
  );
};

Analytics.propTypes = {
  gtmIds: PropTypes.arrayOf(PropTypes.string),
  gtmClientProperties: PropTypes.shape({}),
  gtmPageViewProperties: PropTypes.shape({}),
  gaIds: PropTypes.arrayOf(PropTypes.string),
  gaCustomDimensions: PropTypes.shape({}),
  comScoreIds: PropTypes.arrayOf(
    PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  ),
  title: PropTypes.string.isRequired,
};

Analytics.defaultProps = {
  gtmIds: [],
  gtmClientProperties: {},
  gtmPageViewProperties: {},
  gaIds: [],
  gaCustomDimensions: {},
  comScoreIds: [],
};

const injectNotObserver = fn =>
  compose(
    getContext({ mobxStores: PropTypes.shape({}) }),
    mapProps(({ mobxStores }) => fn(mobxStores)),
  );

export default injectNotObserver(({ stores: { analytics, connection } }) => ({
  gtmIds: analytics.googleTagManager.ids,
  gtmClientProperties: analytics.googleTagManager.clientProperties,
  gtmPageViewProperties: analytics.googleTagManager.pageViewProperties,
  gaIds: analytics.googleAnalytics.ids,
  gaCustomDimensions: analytics.customDimensions(connection.selectedItem),
  comScoreIds: analytics.comScore.ids,
  title: decode(connection.head.title).replace(/<\/?[^>]+(>|$)/g, ''),
}))(Analytics);
<commit_msg>fix(analytics): fixes error when no analytics settings are available<commit_after>/* eslint-disable react/no-danger */
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { compose, getContext, mapProps } from 'recompose';
import { decode } from 'he';
import GoogleTagManager from './GoogleTagManager';
import GoogleAnalytics from './GoogleAnalytics';
import ComScore from './ComScore';

const Analytics = ({
  gtmIds,
  gtmClientProperties,
  gtmPageViewProperties,
  gaIds,
  gaCustomDimensions,
  comScoreIds,
  title,
}) => {
  const { title: _, ...gtmPageView } = gtmPageViewProperties;
  return (
    <Fragment>
      <script
        dangerouslySetInnerHTML={{
          __html: `
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  'gtm.start': Date.now(),
  event: 'gtm.js',
});
window.dataLayer.push({
  event: 'wpPwaProperties',
  wpPwaProperties: ${JSON.stringify(gtmClientProperties)},
});
window.dataLayer.push({
  event: 'virtualPageview',
  virtualPageview: ${JSON.stringify({ title, ...gtmPageView })},
});`,
        }}
      />
      {/* <GoogleTagManager key="GTM-K3S2BMT" id="GTM-K3S2BMT" /> */}
      {gtmIds.map(id => <GoogleTagManager key={id} id={id} />)}
      {gaIds.map(id => (
        <GoogleAnalytics
          key={id}
          id={id}
          customDimensions={gaCustomDimensions}
        />
      ))}
      {comScoreIds.map(id => <ComScore key={id} id={id} />)}
    </Fragment>
  );
};

Analytics.propTypes = {
  gtmIds: PropTypes.arrayOf(PropTypes.string),
  gtmClientProperties: PropTypes.shape({}),
  gtmPageViewProperties: PropTypes.shape({}),
  gaIds: PropTypes.arrayOf(PropTypes.string),
  gaCustomDimensions: PropTypes.shape({}),
  comScoreIds: PropTypes.arrayOf(
    PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  ),
  title: PropTypes.string.isRequired,
};

Analytics.defaultProps = {
  gtmIds: [],
  gtmClientProperties: {},
  gtmPageViewProperties: {},
  gaIds: [],
  gaCustomDimensions: {},
  comScoreIds: [],
};

const injectNotObserver = fn =>
  compose(
    getContext({ mobxStores: PropTypes.shape({}) }),
    mapProps(({ mobxStores }) => fn(mobxStores)),
  );

export default injectNotObserver(({ stores: { analytics, connection } }) => {
  const gtmIds = analytics.googleTagManager.ids;

  return {
    gtmIds,
    gtmClientProperties: gtmIds.length
      ? analytics.googleTagManager.clientProperties
      : {},
    gtmPageViewProperties: gtmIds.length
      ? analytics.googleTagManager.pageViewProperties
      : {},
    gaIds: analytics.googleAnalytics.ids,
    gaCustomDimensions: analytics.customDimensions(connection.selectedItem),
    comScoreIds: analytics.comScore.ids,
    title: decode(connection.head.title).replace(/<\/?[^>]+(>|$)/g, ''),
  };
})(Analytics);
<|endoftext|>


<commit_before>/**
 * Authorization Library.
 *
 * This source code is licensed under the MIT-style license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * Author: Chris Zieba (zieba.chris@gmail.com)
*/

var dodgercms = dodgercms || {};

dodgercms.auth = (function() {
  'use strict';

  /**
   * Get authentication crendentials for the buckets.
   *
   * @param {Boolean} allOrNothing Optional parameter to return null if any of the crendtials are null
   * @return {Object}
  */
  var getCredentials = function(allOrNothing) {
    var credentials = {
      dataBucket: localStorage.getItem('dodgercms-data-bucket'),
      assetsBucket: localStorage.getItem('dodgercms-assets-bucket'),
      siteBucket: localStorage.getItem('dodgercms-site-bucket'),
      accessKey: localStorage.getItem('dodgercms-access-key-id'),
      accessSecret: localStorage.getItem('dodgercms-secret-access-key')
    };

    // Exit if any credentials are missing
    if (allOrNothing) {
      for (var property in credentials) {
        if (credentials.hasOwnProperty(property)) {
          if (!credentials[property]) {
            return null;
          }
        }
      }
    }

    return credentials;
  };

  /**
   * Returns an endpoint for the application.
   *
   * @param {String} protocol Either http or https
   * @param {String} bucket The bucket being referenced
   * @param {String} location The data center the bucket resided in
   * @return {String}
  */
  var getEndpoint = function(protocol, bucket, location) {
    // Check for empty string because of bug in AWS SDK (https://github.com/aws/aws-sdk-js/issues/590)
    if (location === '') {
      location = 'us-east-1';
    } else if (location === 'EU') {
      location = 'eu-west-1';
    }

    var endpoint = protocol + bucket + '.s3-website-' + location + '.amazonaws.com/';

    return endpoint;
  };

  /**
   * Get an IAM policy for the federated user.
   *
   * @param {String} dataBucket The bucket for the markdown content
   * @param {String} assetsBucket The bucket for the uploads
   * @param {String} siteBucket The bucket for the front end website
   * @return {String}
  */
  var getPolicy = function(dataBucket, assetsBucket, siteBucket) {
    // This is the same policy as the user from where the token was generated
    var policy = '{' +
      '"Version": "2012-10-17",' +
      '"Statement": [' +
        '{' +
          '"Sid": "Stmt1427944232000",' +
          '"Effect": "Allow",' +
          '"Action": [' +
            '"s3:ListBucket",' +
            '"s3:GetObject",' +
            '"s3:DeleteObject",' +
            '"s3:PutObject",' +
            '"s3:GetBucketWebsite",' +
            '"s3:PutBucketWebsite",' +
            '"s3:DeleteBucketWebsite",' +
            '"s3:GetBucketLogging",' +
            '"s3:GetBucketVersioning",' +
            '"s3:GetBucketLocation"' +
          '],' +
          '"Resource": [' +
            '"arn:aws:s3:::' + dataBucket + '",' +
            '"arn:aws:s3:::' + dataBucket + '/*",' +
            '"arn:aws:s3:::' + assetsBucket + '",' +
            '"arn:aws:s3:::' + assetsBucket + '/*",' +
            '"arn:aws:s3:::' + siteBucket + '",' +
            '"arn:aws:s3:::' + siteBucket + '/*"' +
          ']' +
        '}' +
      ']' +
    '}';

    return policy;
  };

  /**
   * Handles the federated token creation and policy generation.
   *
   * @param {Object} params The bucket for the markdown content
   * @param {Boolean} callback
  */
  function login(params, callback) {
    // No params, just a callback
    if (arguments.length === 1) {
      // Swap the arguments
      callback = params;

      // Try and get the login info from local storage
      params = getCredentials(true);

      if (!params) {
        // The info was not present in storage, therefore the user needs to enter them again
        return callback('Could not login due to lack of user credentials.');
      }

      // Since they were already remembered
      params.remember = true;
    }

    // Make sure all fields are present
    var required = ['dataBucket', 'assetsBucket', 'siteBucket', 'accessKey', 'accessSecret'];
    for (var i=0; i < required.length; i+=1) {
      if (!params.hasOwnProperty(required[i]) && params[required[i]]) {
        // Pass the error message back
        return callback(required[i] + ' is a required field.');
      }
    }

    params.remember = params.hasOwnProperty('remember') ? Boolean(params.remember) : false;

    var sts = new AWS.STS({
      accessKeyId: params.accessKey,
      secretAccessKey: params.accessSecret,
      sslEnabled: true,
      apiVersion: '2011-06-15'
    });

    // This is the same policy as the IAM user from where the token was generated
    var policy = getPolicy(params.dataBucket, params.assetsBucket, params.siteBucket);

    var stsParams = {
      Name: 'dodgercms-buckets-policy',
      Policy: policy,
      // Duration of 36 hours
      DurationSeconds: 129600,
    };

    // Create the federated token for access to the buckets
    sts.getFederationToken(stsParams, function(err, data) {
      if (err) {
        return callback('Access Denied. Please make sure the acccess key and secret are correct and try again.');
      } else {
        var s3 = new AWS.S3({
          accessKeyId: data.Credentials.AccessKeyId,
          secretAccessKey: data.Credentials.SecretAccessKey,
          sessionToken: data.Credentials.SessionToken,
          sslEnabled: true
        });

        // Test the connection to each bucket
        async.each([params.dataBucket, params.assetsBucket], function(bucket, cb) {
          s3.headBucket({ Bucket: bucket }, function(err) {
            if (err) {
              cb('Access Denied. Please make sure the user attached to the access key has access to ' + bucket + '.');
            } else {
              cb();
            }
          });
        },
        // Handle any errors from the calls to the buckets
        function(err) {
          if (err) {
            return callback(err);
          } else {
            // Get the webiste information. This is needed so we can get the
            // domain and any redirect rules that we need.
            async.parallel([
              function(cb1) {
                s3.getBucketWebsite({ Bucket: params.siteBucket }, function(err, websiteData) {
                  if (err) {
                    cb1('Access Denied. Please make sure the user attached to the access key has access to ' + params.siteBucket + '.');
                  } else {
                    // Success
                    cb1(null, websiteData);
                  }
                });
              },
              function(cb1) {
                s3.getBucketLocation({ Bucket: params.siteBucket }, function(err, locationData) {
                  if (err) {
                    cb1('Access Denied. Please make sure the user attached to the access key has access to ' + params.siteBucket + '.');
                  } else {
                    // Success
                    cb1(null, locationData);
                  }
                });
              }
            ],
            function(err, results) {
              if (err) {
                callback(err);
              } else {
                //var website = results[0];
                var location = results[1];

                // This property will not exist if the bucket is "US Standard" (https://github.com/aws/aws-sdk-js/issues/590)
                var locationConstraint = (location.hasOwnProperty('LocationConstraint')) ? location.LocationConstraint : '';

                // The endpoint is needed for the templates
                var endpoint = getEndpoint('http://', params.siteBucket, locationConstraint);

                // Store the federated user and bucket in local session data
                sessionStorage.setItem('dodgercms-token-access-key-id', data.Credentials.AccessKeyId);
                sessionStorage.setItem('dodgercms-token-secret-access-key', data.Credentials.SecretAccessKey);
                sessionStorage.setItem('dodgercms-token-session-token', data.Credentials.SessionToken);

                // If the user selected "remember me" store their access key and secret in local storage
                if (params.remember) {
                  localStorage.setItem('dodgercms-access-key-id', params.accessKey);
                  localStorage.setItem('dodgercms-secret-access-key', params.accessSecret);
                }

                localStorage.setItem('dodgercms-data-bucket', params.dataBucket);
                localStorage.setItem('dodgercms-assets-bucket', params.assetsBucket);
                localStorage.setItem('dodgercms-site-bucket', params.siteBucket);
                localStorage.setItem('dodgercms-site-endpoint', endpoint);

                // All done
                callback(null, data);
              }
            });
          }
        });
      }
    });
  }

  /**
   * Removes the saved data from local storage.
  */
  function logout() {
    localStorage.removeItem('dodgercms-access-key-id');
    localStorage.removeItem('dodgercms-secret-access-key');
    localStorage.removeItem('dodgercms-data-bucket');
    localStorage.removeItem('dodgercms-assets-bucket');
    localStorage.removeItem('dodgercms-site-bucket');
    localStorage.removeItem('dodgercms-site-endpoint');

    redirect();
  }

  /**
   * Redirect the page.
  */
  function redirect(uri) {
    if (!uri) {
      // Use the login page as the default
      uri = location.protocol + '//' + location.host + '/login';
    }

    window.location.replace(uri);
  }

  return {
    login: login,
    logout: logout,
    redirect: redirect
  };
}());<commit_msg>Redirect to /login.html<commit_after>/**
 * Authorization Library.
 *
 * This source code is licensed under the MIT-style license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * Author: Chris Zieba (zieba.chris@gmail.com)
*/

var dodgercms = dodgercms || {};

dodgercms.auth = (function() {
  'use strict';

  /**
   * Get authentication crendentials for the buckets.
   *
   * @param {Boolean} allOrNothing Optional parameter to return null if any of the crendtials are null
   * @return {Object}
  */
  var getCredentials = function(allOrNothing) {
    var credentials = {
      dataBucket: localStorage.getItem('dodgercms-data-bucket'),
      assetsBucket: localStorage.getItem('dodgercms-assets-bucket'),
      siteBucket: localStorage.getItem('dodgercms-site-bucket'),
      accessKey: localStorage.getItem('dodgercms-access-key-id'),
      accessSecret: localStorage.getItem('dodgercms-secret-access-key')
    };

    // Exit if any credentials are missing
    if (allOrNothing) {
      for (var property in credentials) {
        if (credentials.hasOwnProperty(property)) {
          if (!credentials[property]) {
            return null;
          }
        }
      }
    }

    return credentials;
  };

  /**
   * Returns an endpoint for the application.
   *
   * @param {String} protocol Either http or https
   * @param {String} bucket The bucket being referenced
   * @param {String} location The data center the bucket resided in
   * @return {String}
  */
  var getEndpoint = function(protocol, bucket, location) {
    // Check for empty string because of bug in AWS SDK (https://github.com/aws/aws-sdk-js/issues/590)
    if (location === '') {
      location = 'us-east-1';
    } else if (location === 'EU') {
      location = 'eu-west-1';
    }

    var endpoint = protocol + bucket + '.s3-website-' + location + '.amazonaws.com/';

    return endpoint;
  };

  /**
   * Get an IAM policy for the federated user.
   *
   * @param {String} dataBucket The bucket for the markdown content
   * @param {String} assetsBucket The bucket for the uploads
   * @param {String} siteBucket The bucket for the front end website
   * @return {String}
  */
  var getPolicy = function(dataBucket, assetsBucket, siteBucket) {
    // This is the same policy as the user from where the token was generated
    var policy = '{' +
      '"Version": "2012-10-17",' +
      '"Statement": [' +
        '{' +
          '"Sid": "Stmt1427944232000",' +
          '"Effect": "Allow",' +
          '"Action": [' +
            '"s3:ListBucket",' +
            '"s3:GetObject",' +
            '"s3:DeleteObject",' +
            '"s3:PutObject",' +
            '"s3:GetBucketWebsite",' +
            '"s3:PutBucketWebsite",' +
            '"s3:DeleteBucketWebsite",' +
            '"s3:GetBucketLogging",' +
            '"s3:GetBucketVersioning",' +
            '"s3:GetBucketLocation"' +
          '],' +
          '"Resource": [' +
            '"arn:aws:s3:::' + dataBucket + '",' +
            '"arn:aws:s3:::' + dataBucket + '/*",' +
            '"arn:aws:s3:::' + assetsBucket + '",' +
            '"arn:aws:s3:::' + assetsBucket + '/*",' +
            '"arn:aws:s3:::' + siteBucket + '",' +
            '"arn:aws:s3:::' + siteBucket + '/*"' +
          ']' +
        '}' +
      ']' +
    '}';

    return policy;
  };

  /**
   * Handles the federated token creation and policy generation.
   *
   * @param {Object} params The bucket for the markdown content
   * @param {Boolean} callback
  */
  function login(params, callback) {
    // No params, just a callback
    if (arguments.length === 1) {
      // Swap the arguments
      callback = params;

      // Try and get the login info from local storage
      params = getCredentials(true);

      if (!params) {
        // The info was not present in storage, therefore the user needs to enter them again
        return callback('Could not login due to lack of user credentials.');
      }

      // Since they were already remembered
      params.remember = true;
    }

    // Make sure all fields are present
    var required = ['dataBucket', 'assetsBucket', 'siteBucket', 'accessKey', 'accessSecret'];
    for (var i=0; i < required.length; i+=1) {
      if (!params.hasOwnProperty(required[i]) && params[required[i]]) {
        // Pass the error message back
        return callback(required[i] + ' is a required field.');
      }
    }

    params.remember = params.hasOwnProperty('remember') ? Boolean(params.remember) : false;

    var sts = new AWS.STS({
      accessKeyId: params.accessKey,
      secretAccessKey: params.accessSecret,
      sslEnabled: true,
      apiVersion: '2011-06-15'
    });

    // This is the same policy as the IAM user from where the token was generated
    var policy = getPolicy(params.dataBucket, params.assetsBucket, params.siteBucket);

    var stsParams = {
      Name: 'dodgercms-buckets-policy',
      Policy: policy,
      // Duration of 36 hours
      DurationSeconds: 129600,
    };

    // Create the federated token for access to the buckets
    sts.getFederationToken(stsParams, function(err, data) {
      if (err) {
        return callback('Access Denied. Please make sure the acccess key and secret are correct and try again.');
      } else {
        var s3 = new AWS.S3({
          accessKeyId: data.Credentials.AccessKeyId,
          secretAccessKey: data.Credentials.SecretAccessKey,
          sessionToken: data.Credentials.SessionToken,
          sslEnabled: true
        });

        // Test the connection to each bucket
        async.each([params.dataBucket, params.assetsBucket], function(bucket, cb) {
          s3.headBucket({ Bucket: bucket }, function(err) {
            if (err) {
              cb('Access Denied. Please make sure the user attached to the access key has access to ' + bucket + '.');
            } else {
              cb();
            }
          });
        },
        // Handle any errors from the calls to the buckets
        function(err) {
          if (err) {
            return callback(err);
          } else {
            // Get the webiste information. This is needed so we can get the
            // domain and any redirect rules that we need.
            async.parallel([
              function(cb1) {
                s3.getBucketWebsite({ Bucket: params.siteBucket }, function(err, websiteData) {
                  if (err) {
                    cb1('Access Denied. Please make sure the user attached to the access key has access to ' + params.siteBucket + '.');
                  } else {
                    // Success
                    cb1(null, websiteData);
                  }
                });
              },
              function(cb1) {
                s3.getBucketLocation({ Bucket: params.siteBucket }, function(err, locationData) {
                  if (err) {
                    cb1('Access Denied. Please make sure the user attached to the access key has access to ' + params.siteBucket + '.');
                  } else {
                    // Success
                    cb1(null, locationData);
                  }
                });
              }
            ],
            function(err, results) {
              if (err) {
                callback(err);
              } else {
                //var website = results[0];
                var location = results[1];

                // This property will not exist if the bucket is "US Standard" (https://github.com/aws/aws-sdk-js/issues/590)
                var locationConstraint = (location.hasOwnProperty('LocationConstraint')) ? location.LocationConstraint : '';

                // The endpoint is needed for the templates
                var endpoint = getEndpoint('http://', params.siteBucket, locationConstraint);

                // Store the federated user and bucket in local session data
                sessionStorage.setItem('dodgercms-token-access-key-id', data.Credentials.AccessKeyId);
                sessionStorage.setItem('dodgercms-token-secret-access-key', data.Credentials.SecretAccessKey);
                sessionStorage.setItem('dodgercms-token-session-token', data.Credentials.SessionToken);

                // If the user selected "remember me" store their access key and secret in local storage
                if (params.remember) {
                  localStorage.setItem('dodgercms-access-key-id', params.accessKey);
                  localStorage.setItem('dodgercms-secret-access-key', params.accessSecret);
                }

                localStorage.setItem('dodgercms-data-bucket', params.dataBucket);
                localStorage.setItem('dodgercms-assets-bucket', params.assetsBucket);
                localStorage.setItem('dodgercms-site-bucket', params.siteBucket);
                localStorage.setItem('dodgercms-site-endpoint', endpoint);

                // All done
                callback(null, data);
              }
            });
          }
        });
      }
    });
  }

  /**
   * Removes the saved data from local storage.
  */
  function logout() {
    localStorage.removeItem('dodgercms-access-key-id');
    localStorage.removeItem('dodgercms-secret-access-key');
    localStorage.removeItem('dodgercms-data-bucket');
    localStorage.removeItem('dodgercms-assets-bucket');
    localStorage.removeItem('dodgercms-site-bucket');
    localStorage.removeItem('dodgercms-site-endpoint');

    redirect();
  }

  /**
   * Redirect the page.
  */
  function redirect(uri) {
    if (!uri) {
      // Use the login page as the default
      uri = location.protocol + '//' + location.host + '/login.html';
    }

    window.location.replace(uri);
  }

  return {
    login: login,
    logout: logout,
    redirect: redirect
  };
}());
<|endoftext|>


<commit_before>// (c) dynarch.com 2003-2004
// Distributed under the same terms as HTMLArea itself.

function PopupWin(editor, title, handler, initFunction) {
	this.editor = editor;
	this.handler = handler;
	var dlg = window.open("", "__ha_dialog",
			      "toolbar=no,menubar=no,personalbar=no,width=600,height=600,left=20,top=40" +
			      "scrollbars=no,resizable=no");
	this.window = dlg;
	var doc = dlg.document;
	this.doc = doc;
	var self = this;

	var base = document.baseURI || document.URL;
	if (base && base.match(/(.*)\/([^\/]+)/)) {
		base = RegExp.$1 + "/";
	}
	if (typeof _editor_url != "undefined" && !/^\//.test(_editor_url) && !/http:\/\//.test(_editor_url)) {
		// _editor_url doesn't start with '/' which means it's relative
		// FIXME: there's a problem here, it could be http:// which
		// doesn't start with slash but it's not relative either.
		base += _editor_url;
	} else
		base = _editor_url;
	if (!/\/$/.test(base)) {
		// base does not end in slash, add it now
		base += '/';
	}
	this.baseURL = base;

	doc.open();
	var html = "<title>" + title + "</title>\n";
	// html += "<base href='" + base + "htmlarea.js' />\n";
	html += "\n";
	html += "";
	doc.write(html);
	doc.close();

	// sometimes I Hate Mozilla... ;-(
	function init2() {
		var body = doc.body;
		if (!body) {
			setTimeout(init2, 25);
			return false;
		}
		dlg.title = title;
		doc.documentElement.style.padding = "0px";
		doc.documentElement.style.margin = "0px";
		var content = doc.createElement("div");
		content.className = "content";
		self.content = content;
		body.appendChild(content);
		self.element = body;
		initFunction(self);
		dlg.focus();
	};
	init2();
};

PopupWin.prototype.callHandler = function() {
	var tags = ["input", "textarea", "select"];
	var params = new Object();
	for (var ti = tags.length; --ti >= 0;) {
		var tag = tags[ti];
		var els = this.content.getElementsByTagName(tag);
		for (var j = 0; j < els.length; ++j) {
			var el = els[j];
			var val = el.value;
			if (el.tagName.toLowerCase() == "input") {
				if (el.type == "checkbox") {
					val = el.checked;
				}
			}
			params[el.name] = val;
		}
	}
	this.handler(this, params);
	return false;
};

PopupWin.prototype.close = function() {
	this.window.close();
};

PopupWin.prototype.addButtons = function() {
	var self = this;
	var div = this.doc.createElement("div");
	this.content.appendChild(div);
	div.className = "buttons";
	for (var i = 0; i < arguments.length; ++i) {
		var btn = arguments[i];
		var button = this.doc.createElement("button");
		div.appendChild(button);
		button.innerHTML = HTMLArea._lc(btn, 'HTMLArea');
		switch (btn) {
		    case "ok":
			button.onclick = function() {
				self.callHandler();
				self.close();
				return false;
			};
			break;
		    case "cancel":
			button.onclick = function() {
				self.close();
				return false;
			};
			break;
		}
	}
};

PopupWin.prototype.showAtElement = function() {
	var self = this;
	// Mozilla needs some time to realize what's goin' on..
	setTimeout(function() {
		var w = self.content.offsetWidth + 4;
		var h = self.content.offsetHeight + 4;
		// size to content -- that's fuckin' buggy in all fuckin' browsers!!!
		// so that we set a larger size for the dialog window and then center
		// the element inside... phuck!

		// center...
		var el = self.content;
		var s = el.style;
		// s.width = el.offsetWidth + "px";
		// s.height = el.offsetHeight + "px";
		s.position = "absolute";
		s.left = (w - el.offsetWidth) / 2 + "px";
		s.top = (h - el.offsetHeight) / 2 + "px";
		if (HTMLArea.is_gecko) {
			self.window.innerWidth = w;
			self.window.innerHeight = h;
		} else {
			self.window.resizeTo(w + 8, h + 35);
		}
	}, 25);
};
<commit_msg>fixed #174 (fixes bug introduced in changeset:81)<commit_after>// (c) dynarch.com 2003-2004
// Distributed under the same terms as HTMLArea itself.

function PopupWin(editor, title, handler, initFunction) {
	this.editor = editor;
	this.handler = handler;
	var dlg = window.open("", "__ha_dialog",
			      "toolbar=no,menubar=no,personalbar=no,width=600,height=600,left=20,top=40" +
			      "scrollbars=no,resizable=no");
	this.window = dlg;
	var doc = dlg.document;
	this.doc = doc;
	var self = this;

	var base = document.baseURI || document.URL;
	if (base && base.match(/(.*)\/([^\/]+)/)) {
		base = RegExp.$1 + "/";
	}
	if (typeof _editor_url != "undefined" && !/^\//.test(_editor_url) && !/http:\/\//.test(_editor_url)) {
		// _editor_url doesn't start with '/' which means it's relative
		// FIXME: there's a problem here, it could be http:// which
		// doesn't start with slash but it's not relative either.
		base += _editor_url;
	} else
		base = _editor_url;
	if (!/\/$/.test(base)) {
		// base does not end in slash, add it now
		base += '/';
	}
	this.baseURL = base;

	doc.open();
	var html = "<title>" + title + "</title>\n";
	// html += "<base href='" + base + "htmlarea.js' />\n";
	html += "\n";
	html += "";
	doc.write(html);
	doc.close();

	// sometimes I Hate Mozilla... ;-(
	function init2() {
		var body = doc.body;
		if (!body) {
			setTimeout(init2, 25);
			return false;
		}
		dlg.title = title;
		doc.documentElement.style.padding = "0px";
		doc.documentElement.style.margin = "0px";
		var content = doc.createElement("div");
		content.className = "content";
		self.content = content;
		body.appendChild(content);
		self.element = body;
		initFunction(self);
		dlg.focus();
	};
	init2();
};

PopupWin.prototype.callHandler = function() {
	var tags = ["input", "textarea", "select"];
	var params = new Object();
	for (var ti = tags.length; --ti >= 0;) {
		var tag = tags[ti];
		var els = this.content.getElementsByTagName(tag);
		for (var j = 0; j < els.length; ++j) {
			var el = els[j];
			var val = el.value;
			if (el.tagName.toLowerCase() == "input") {
				if (el.type == "checkbox") {
					val = el.checked;
				}
			}
			params[el.name] = val;
		}
	}
	this.handler(this, params);
	return false;
};

PopupWin.prototype.close = function() {
	this.window.close();
};

PopupWin.prototype.addButtons = function() {
	var self = this;
	var div = this.doc.createElement("div");
	this.content.appendChild(div);
	div.className = "buttons";
	for (var i = 0; i < arguments.length; ++i) {
		var btn = arguments[i];
		var button = this.doc.createElement("button");
		div.appendChild(button);
		button.innerHTML = HTMLArea._lc(btn, 'HTMLArea');
		switch (btn.toLowerCase()) {
		    case "ok":
			button.onclick = function() {
				self.callHandler();
				self.close();
				return false;
			};
			break;
		    case "cancel":
			button.onclick = function() {
				self.close();
				return false;
			};
			break;
		}
	}
};

PopupWin.prototype.showAtElement = function() {
	var self = this;
	// Mozilla needs some time to realize what's goin' on..
	setTimeout(function() {
		var w = self.content.offsetWidth + 4;
		var h = self.content.offsetHeight + 4;
		// size to content -- that's fuckin' buggy in all fuckin' browsers!!!
		// so that we set a larger size for the dialog window and then center
		// the element inside... phuck!

		// center...
		var el = self.content;
		var s = el.style;
		// s.width = el.offsetWidth + "px";
		// s.height = el.offsetHeight + "px";
		s.position = "absolute";
		s.left = (w - el.offsetWidth) / 2 + "px";
		s.top = (h - el.offsetHeight) / 2 + "px";
		if (HTMLArea.is_gecko) {
			self.window.innerWidth = w;
			self.window.innerHeight = h;
		} else {
			self.window.resizeTo(w + 8, h + 35);
		}
	}, 25);
};
<|endoftext|>


<commit_before>var ns = require("./ns"),
  socket = require("./socket"),
  channels = require("./channels"),
  cache = require("./simple-cache"),
  util = require("util"),
  _ = require("underscore")._;

var DEFAULT_AUTHORITY = "guest";

function resolveAuthorities(authorities) {
  var curr,
      type,
      outAuths = [];

  for (var i = 0; i < authorities.length; i++) {
    curr = authorities[i];
    type = typeof(curr);
    if (type === 'string') {
      outAuths.push(curr);
    } else if (type === 'object') {
      // TODO:
      // The auth is an object (such as a lease).  Resolve it into a string "role".
      // If it is a lease, validate the lease (expiration etc.).  If the lease is
      // still valid, push the name onto the list.
    }
  }

  return outAuths;
}

/**
 * @class represents a user object on the server.  Instances are usually stuffed in a user's session.
 * @property {String} username the user's username
 * @property {Object} profile the user's profile object, if available
 * @property {Array} authorities an array of the authorities (roles) a user has granted to them
 * @property {boolean} authenticated true if the user is logged in
 */
var User = function(options) {
  options = options || {};
  this.username = options.username || 'anonymous';
  this.profile = options.profile || null;
  this.authorities = options.authorities || [DEFAULT_AUTHORITY];
  this.authenticated = false;
  if (options.authenticated) {
    this.authenticated = true;
  }
};

/**
 * @class manages all auth communication between client and server.
 */
var api = exports.api = /** @lends api.prototype */ {
  /**
   * note: making this a method call to support adding other conditions in the future without requiring api changes
   * @returns {boolean} the value of "authenticated".
   */
  isLoggedIn: function(user) {
    return user.authenticated;
  },
  /**
   * @param {String} named the authority to test for
   * @returns {boolean} returns true if the user has the named authority
   */
  hasAuthority: function(user, named) {
    if (typeof named === "string") {
      return (user.authorities && user.authorities.indexOf(named) >= 0);
    }
    return false;
  },
  /**
   * @param {Array} list an array of named authorities to test for
   * @returns {boolean} returns true if the user has any of the authorities in the given list
   */
  hasAnyAuthority: function(user, list) {
    for (var i = 0; i < list.length; i++) {
      if (api.hasAuthority(user, list[i])) return true;
    }
    return false;
  },
  /**
   * @param {Array} list an array of named authorities to test for
   * @returns {boolean} returns true if the user has all of the authorities in the given list
   */
  hasAllAuthorities: function(user, list) {
    for (var i = 0; i < list.length; i++) {
      if (!this.hasAuthority(user, list[i])) return false;
    }
    return true;
  },
  /**
   * Logs in by session id, username, and password.
   * If no sessionId is provided, an attempt will be made to use the current request's session.
   * If successful, the resulting user is passed into the callback function, otherwise an error is passed.
   * @param {String} sessionId The sessionId to automatically store the returned user object in, or null is no storage is desired
   * @param {String} username Username of the user to login
   * @param {String} password Password of the user to login
   * @param {Function} cb Callback function to call with result of the login attempt
   * @return undefined
   */
  loginBySessionId: function(sessionId, username, password, cb) {
    cb = cb || feather.emptyFn;
    if (!sessionId) {
      cb("Session id is required for loginBySessionId method.");
    } else {
      if (!username || !password) {
        cb("No username or password submitted.");
      }

      cache.getItemsWait([
        "feather-authdb",
        "feather-server",
        "feather-logger",
        "feather-options"
      ], function(err, cacheItems) {
        if (err) cb(err); else {
          var authdb = cacheItems["feather-authdb"],
            server = cacheItems["feather-server"],
            logger = cacheItems["feather-logger"],
            appOptions = cacheItems["feather-options"];

          authdb.get(appOptions.auth.userIdPrefix + username, function(err, dbResult) {
            if (!err) {
              if (dbResult.password === password) {
                var user = new User({
                  authenticated: true,
                  profile: dbResult.profile || {},
                  username: (dbResult.profile && dbResult.profile.username) || dbResult.username || dbResult.name,
                  authorities: resolveAuthorities(dbResult.roles || dbResult.authorities)
                });
                server.sessionStore.get(sessionId, function(err, sess) {
                  if (!err) {
                    sess.user = user;
                    server.sessionStore.set(sessionId, sess);
                    cb(null, {user: user});
                  } else {
                    cb(err);
                  }
                });
              } else {
                logger.warn("Invalid login attempt for username '" + username + "'");
                cb("Invalid username/password");
              }
            } else {
              if (err.error && err.error === "not_found") {
                err = "Username " + username + " not found.";
              }
              cb(err);
            }
          });
        }
      });
    } //end else
  },
  /**
   * Destroys the user's session before returning
   * @param {String} sessionId The session id to use to retrieve the user for.
   * @param {Function} cb Callback function to be called after logout.
   * @return undefined
   */
  logoutBySessionId: function(sessionId, cb) {
    if (!sessionId) {
      cb && cb("Session id is required for loginBySessionId method.");
    } else {
      cache.getItemsWait([
        "feather-server",
        "feather-logger"
      ], function(err, cacheItems) {
        if (err) cb(err); else {
          var server = cacheItems["feather-server"],
            logger = cacheItems["feather-logger"];

          server.sessionStore.get(sessionId, function(err, sess) {
            if (err) cb(err); else {
              server.sessionStore.destroy(sessionId, function() {
                //after destroying the existing session, generate and store a new one, and pass the new sid back to the client
                var dummyReq = {headers:{}};
                server.sessionStore.generate(dummyReq);
                server.sessionStore.set(dummyReq.session.id, dummyReq.session);
                logger.info("new session ID = " + dummyReq.session.id);
                cb(null, dummyReq.session);
              });
            }
          });
        }
      });
    }
  },
  /**
   * Not implemented.
   */
   signup: function(options) {

   },
  /**
   * Not implemented.
   */
   getAuthorities: function(options) {

   },
  /**
   * Retrieves the user associated with the session matching the passed in sessionId.
   * If successful, the resulting user is passed into the callback function, otherwise an error is passed.
   * @param {String} sessionId The session id to use to retrieve the user for.
   * @param {Function} cb A callback function that is called with the user object as the 2nd argument if successful. If an error occurred retrieving the session, the error is passed to the callback as the first argument.
   * @return undefined
   */
  getUserBySessionId: function(sessionId, cb) {
    if (!sessionId) {
      cb("Session id is required for getUserBySessionId method.");
    } else {
      cache.getItemsWait([
        "feather-server"
      ], function(err, cacheItems) {
        if (err) cb(err); else {
          var server = cacheItems["feather-server"];

          server.sessionStore.get(sessionId, function(err, sess) {
            if (err) cb(err); else {
              if (sess) {
                cb(null, {user: sess.user});
              } else {
                cb();
              }
            }
          });
        }
      });
    }
  }
};

// var authChannel = channels.addChannel({
//   id: "feather:auth",
//   messages: ["login", "getUser", "logout"],
//   announceConnections: false,
//   hooks: {
//     message: function(args, cb) {
//       //this channel won't propagate messages, it's just used for internal messaging

//     }
//   }
// });

//TODO: migrate these handlers to proper channels implementation (preferably via flight services)

cache.getItemsWait([
  "feather-logger"
], function(err, cacheItems) {
  if (err) throw err;

  var logger = cacheItems["feather-logger"];

  // initialize event listeners
  socket.on("auth:login", function(args) {
    var eventArgs = args.message.data.eventArgs;
    api.loginBySessionId(args.message.sid, eventArgs.username, eventArgs.password, function(err, loginResult) {
      var result = {success: true};
      if (err) {
        result.success = false;
        result.err = err;
      } else {
        result.user = loginResult.user;
      }
      args.result.eventArgs = result;
      args.client.json.send(args.result);
    });
  });

  socket.on("auth:getUser", function(args) {
    api.getUserBySessionId(args.message.sid, function(err, getUserResult) {
      var result = {success: true};
      if (err || !getUserResult || !getUserResult.user) {
        result.success = false;
        result.err = err || "No user found.";
        logger.trace("user err: " + result.err);
      } else {
        result.user = getUserResult.user;
        logger.trace("user: " + util.inspect(getUserResult));
      }
      args.result.eventArgs = result;
      args.client.json.send(args.result);
    });
  });

  socket.on("auth:logout", function(args) {
    api.logoutBySessionId(args.message.sid, function(err, sid) {
      var result = {success: true, result: sid};
      if (err) {
        result.success = false;
        result.err = err;
      }
      args.result.eventArgs = result;
      args.client.json.send(args.result);
    });
  });
});
<commit_msg>have to pass sessionStore along with dummy request object when creating the new session<commit_after>var ns = require("./ns"),
  socket = require("./socket"),
  channels = require("./channels"),
  cache = require("./simple-cache"),
  util = require("util"),
  _ = require("underscore")._;

var DEFAULT_AUTHORITY = "guest";

function resolveAuthorities(authorities) {
  var curr,
      type,
      outAuths = [];

  for (var i = 0; i < authorities.length; i++) {
    curr = authorities[i];
    type = typeof(curr);
    if (type === 'string') {
      outAuths.push(curr);
    } else if (type === 'object') {
      // TODO:
      // The auth is an object (such as a lease).  Resolve it into a string "role".
      // If it is a lease, validate the lease (expiration etc.).  If the lease is
      // still valid, push the name onto the list.
    }
  }

  return outAuths;
}

/**
 * @class represents a user object on the server.  Instances are usually stuffed in a user's session.
 * @property {String} username the user's username
 * @property {Object} profile the user's profile object, if available
 * @property {Array} authorities an array of the authorities (roles) a user has granted to them
 * @property {boolean} authenticated true if the user is logged in
 */
var User = function(options) {
  options = options || {};
  this.username = options.username || 'anonymous';
  this.profile = options.profile || null;
  this.authorities = options.authorities || [DEFAULT_AUTHORITY];
  this.authenticated = false;
  if (options.authenticated) {
    this.authenticated = true;
  }
};

/**
 * @class manages all auth communication between client and server.
 */
var api = exports.api = /** @lends api.prototype */ {
  /**
   * note: making this a method call to support adding other conditions in the future without requiring api changes
   * @returns {boolean} the value of "authenticated".
   */
  isLoggedIn: function(user) {
    return user.authenticated;
  },
  /**
   * @param {String} named the authority to test for
   * @returns {boolean} returns true if the user has the named authority
   */
  hasAuthority: function(user, named) {
    if (typeof named === "string") {
      return (user.authorities && user.authorities.indexOf(named) >= 0);
    }
    return false;
  },
  /**
   * @param {Array} list an array of named authorities to test for
   * @returns {boolean} returns true if the user has any of the authorities in the given list
   */
  hasAnyAuthority: function(user, list) {
    for (var i = 0; i < list.length; i++) {
      if (api.hasAuthority(user, list[i])) return true;
    }
    return false;
  },
  /**
   * @param {Array} list an array of named authorities to test for
   * @returns {boolean} returns true if the user has all of the authorities in the given list
   */
  hasAllAuthorities: function(user, list) {
    for (var i = 0; i < list.length; i++) {
      if (!this.hasAuthority(user, list[i])) return false;
    }
    return true;
  },
  /**
   * Logs in by session id, username, and password.
   * If no sessionId is provided, an attempt will be made to use the current request's session.
   * If successful, the resulting user is passed into the callback function, otherwise an error is passed.
   * @param {String} sessionId The sessionId to automatically store the returned user object in, or null is no storage is desired
   * @param {String} username Username of the user to login
   * @param {String} password Password of the user to login
   * @param {Function} cb Callback function to call with result of the login attempt
   * @return undefined
   */
  loginBySessionId: function(sessionId, username, password, cb) {
    cb = cb || feather.emptyFn;
    if (!sessionId) {
      cb("Session id is required for loginBySessionId method.");
    } else {
      if (!username || !password) {
        cb("No username or password submitted.");
      }

      cache.getItemsWait([
        "feather-authdb",
        "feather-server",
        "feather-logger",
        "feather-options"
      ], function(err, cacheItems) {
        if (err) cb(err); else {
          var authdb = cacheItems["feather-authdb"],
            server = cacheItems["feather-server"],
            logger = cacheItems["feather-logger"],
            appOptions = cacheItems["feather-options"];

          authdb.get(appOptions.auth.userIdPrefix + username, function(err, dbResult) {
            if (!err) {
              if (dbResult.password === password) {
                var user = new User({
                  authenticated: true,
                  profile: dbResult.profile || {},
                  username: (dbResult.profile && dbResult.profile.username) || dbResult.username || dbResult.name,
                  authorities: resolveAuthorities(dbResult.roles || dbResult.authorities)
                });
                server.sessionStore.get(sessionId, function(err, sess) {
                  if (!err) {
                    sess.user = user;
                    server.sessionStore.set(sessionId, sess);
                    cb(null, {user: user});
                  } else {
                    cb(err);
                  }
                });
              } else {
                logger.warn("Invalid login attempt for username '" + username + "'");
                cb("Invalid username/password");
              }
            } else {
              if (err.error && err.error === "not_found") {
                err = "Username " + username + " not found.";
              }
              cb(err);
            }
          });
        }
      });
    } //end else
  },
  /**
   * Destroys the user's session before returning
   * @param {String} sessionId The session id to use to retrieve the user for.
   * @param {Function} cb Callback function to be called after logout.
   * @return undefined
   */
  logoutBySessionId: function(sessionId, cb) {
    if (!sessionId) {
      cb && cb("Session id is required for loginBySessionId method.");
    } else {
      cache.getItemsWait([
        "feather-server",
        "feather-logger"
      ], function(err, cacheItems) {
        if (err) cb(err); else {
          var server = cacheItems["feather-server"],
            logger = cacheItems["feather-logger"];

          server.sessionStore.get(sessionId, function(err, sess) {
            if (err) cb(err); else {
              server.sessionStore.destroy(sessionId, function() {
                //after destroying the existing session, generate and store a new one, and pass the new sid back to the client
                var dummyReq = {headers:{}, sessionStore: server.sessionStore};
                server.sessionStore.generate(dummyReq);
                server.sessionStore.set(dummyReq.session.id, dummyReq.session);
                logger.info("new session ID = " + dummyReq.session.id);
                cb(null, dummyReq.session);
              });
            }
          });
        }
      });
    }
  },
  /**
   * Not implemented.
   */
   signup: function(options) {

   },
  /**
   * Not implemented.
   */
   getAuthorities: function(options) {

   },
  /**
   * Retrieves the user associated with the session matching the passed in sessionId.
   * If successful, the resulting user is passed into the callback function, otherwise an error is passed.
   * @param {String} sessionId The session id to use to retrieve the user for.
   * @param {Function} cb A callback function that is called with the user object as the 2nd argument if successful. If an error occurred retrieving the session, the error is passed to the callback as the first argument.
   * @return undefined
   */
  getUserBySessionId: function(sessionId, cb) {
    if (!sessionId) {
      cb("Session id is required for getUserBySessionId method.");
    } else {
      cache.getItemsWait([
        "feather-server"
      ], function(err, cacheItems) {
        if (err) cb(err); else {
          var server = cacheItems["feather-server"];

          server.sessionStore.get(sessionId, function(err, sess) {
            if (err) cb(err); else {
              if (sess) {
                cb(null, {user: sess.user});
              } else {
                cb();
              }
            }
          });
        }
      });
    }
  }
};

// var authChannel = channels.addChannel({
//   id: "feather:auth",
//   messages: ["login", "getUser", "logout"],
//   announceConnections: false,
//   hooks: {
//     message: function(args, cb) {
//       //this channel won't propagate messages, it's just used for internal messaging

//     }
//   }
// });

//TODO: migrate these handlers to proper channels implementation (preferably via flight services)

cache.getItemsWait([
  "feather-logger"
], function(err, cacheItems) {
  if (err) throw err;

  var logger = cacheItems["feather-logger"];

  // initialize event listeners
  socket.on("auth:login", function(args) {
    var eventArgs = args.message.data.eventArgs;
    api.loginBySessionId(args.message.sid, eventArgs.username, eventArgs.password, function(err, loginResult) {
      var result = {success: true};
      if (err) {
        result.success = false;
        result.err = err;
      } else {
        result.user = loginResult.user;
      }
      args.result.eventArgs = result;
      args.client.json.send(args.result);
    });
  });

  socket.on("auth:getUser", function(args) {
    api.getUserBySessionId(args.message.sid, function(err, getUserResult) {
      var result = {success: true};
      if (err || !getUserResult || !getUserResult.user) {
        result.success = false;
        result.err = err || "No user found.";
        logger.trace("user err: " + result.err);
      } else {
        result.user = getUserResult.user;
        logger.trace("user: " + util.inspect(getUserResult));
      }
      args.result.eventArgs = result;
      args.client.json.send(args.result);
    });
  });

  socket.on("auth:logout", function(args) {
    api.logoutBySessionId(args.message.sid, function(err, sid) {
      var result = {success: true, result: sid};
      if (err) {
        result.success = false;
        result.err = err;
      }
      args.result.eventArgs = result;
      args.client.json.send(args.result);
    });
  });
});
<|endoftext|>


<commit_before>import bug from 'debug'
import passport from 'passport'
import { Strategy as GitHubStrategy } from 'passport-github2'
import express from 'express'
import assign from 'lodash/assign'
import User from './db/models/user'
import { exitWithError } from './helpers'

const debug = bug('SIR:auth')
const router = express.Router()
const clientId = process.env.CLIENT_ID
const clientSecret = process.env.CLIENT_SECRET
const nodeEnv = process.env.NODE_ENV

var baseUrl

switch (nodeEnv) {
  case 'production':
    baseUrl = process.env.BASE_URL
    break
  case 'staging':
    baseUrl = process.env.HEROKU_APP_NAME
      ? `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`
      : process.env.BASE_URL
    break

  default:
    baseUrl = process.env.BASE_URL || 'http://localhost:5000'
}

debug('client id', clientId)
debug('client secret', clientSecret)
debug(`Base URL ${baseUrl}/auth/github/callback`)

if (!clientId) {
  exitWithError('Please set CLIENT_ID environment variable.')
}

if (!clientSecret) {
  exitWithError('Please set CLIENT_SECRET environment variable.')
}

passport.serializeUser((user, done) => {
  debug('serialize', user)
  done(null, user.githubId)
})

passport.deserializeUser((githubId, done) => {
  debug('deserialize', githubId)

  User.findOne({ githubId }, (err, user) => {
    debug('deserialize:findOne', err, user)
    done(err, user)
  })
})

passport.use(new GitHubStrategy({
  clientID: clientId,
  clientSecret: clientSecret,
  callbackURL: baseUrl + '/auth/github/callback'
}, (accessToken, refreshToken, profile, done) => {
  debug('GitHub auth successful', profile)

  const id = profile.id
  const githubProfile = assign({ githubId: id }, profile._json)

  debug('githubProfile object', githubProfile)

  User.findOrCreate({ githubId: id }, githubProfile, (err, user, created) => {
    debug('findOrCreate user', err, user, created)
    const githubId = githubProfile.githubId
    const date = {}

    // update user
    if (created) {
      date.createdAt = Date.now()
    } else {
      date.createdAt = user.createdAt || Date.now()
      date.updatedAt = Date.now()
    }

    User.update({ githubId }, assign(githubProfile, date), err => {
      debug('updated user')
      return done(err, user)
    })
  })
}))

router.get('/github', passport.authenticate('github', { scope: 'user' }))

router.get('/github/callback', passport.authenticate('github', {
  successRedirect: '/apply',
  failureRedirect: '/signin'
}))

export default router
<commit_msg>add signout route<commit_after>import bug from 'debug'
import passport from 'passport'
import { Strategy as GitHubStrategy } from 'passport-github2'
import express from 'express'
import assign from 'lodash/assign'
import User from './db/models/user'
import { exitWithError } from './helpers'

const debug = bug('SIR:auth')
const router = express.Router()
const clientId = process.env.CLIENT_ID
const clientSecret = process.env.CLIENT_SECRET
const nodeEnv = process.env.NODE_ENV

var baseUrl

switch (nodeEnv) {
  case 'production':
    baseUrl = process.env.BASE_URL
    break
  case 'staging':
    baseUrl = process.env.HEROKU_APP_NAME
      ? `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`
      : process.env.BASE_URL
    break

  default:
    baseUrl = process.env.BASE_URL || 'http://localhost:5000'
}

debug('client id', clientId)
debug('client secret', clientSecret)
debug(`Base URL ${baseUrl}/auth/github/callback`)

if (!clientId) {
  exitWithError('Please set CLIENT_ID environment variable.')
}

if (!clientSecret) {
  exitWithError('Please set CLIENT_SECRET environment variable.')
}

passport.serializeUser((user, done) => {
  debug('serialize', user)
  done(null, user.githubId)
})

passport.deserializeUser((githubId, done) => {
  debug('deserialize', githubId)

  User.findOne({ githubId }, (err, user) => {
    debug('deserialize:findOne', err, user)
    done(err, user)
  })
})

passport.use(new GitHubStrategy({
  clientID: clientId,
  clientSecret: clientSecret,
  callbackURL: baseUrl + '/auth/github/callback'
}, (accessToken, refreshToken, profile, done) => {
  debug('GitHub auth successful', profile)

  const id = profile.id
  const githubProfile = assign({ githubId: id }, profile._json)

  debug('githubProfile object', githubProfile)

  User.findOrCreate({ githubId: id }, githubProfile, (err, user, created) => {
    debug('findOrCreate user', err, user, created)
    const githubId = githubProfile.githubId
    const date = {}

    // update user
    if (created) {
      date.createdAt = Date.now()
    } else {
      date.createdAt = user.createdAt || Date.now()
      date.updatedAt = Date.now()
    }

    User.update({ githubId }, assign(githubProfile, date), err => {
      debug('updated user')
      return done(err, user)
    })
  })
}))

router.get('/github', passport.authenticate('github', { scope: 'user' }))

router.get('/github/callback', passport.authenticate('github', {
  successRedirect: '/apply',
  failureRedirect: '/signin'
}))

router.get('/github/signout', (req, res) => {
  req.logout()
  res.redirect('/')
})

export default router
<|endoftext|>


<commit_before>"use strict";

var mongoose = require("mongoose");
var passport = require("passport");
var xtend = require("xtend");

module.exports = Auth;

function Auth() {
  // These fields are required for the user schema
  this._userSchema = {
    username: {
      type: String,
      required: true,
      index: {
        unique: true,
        dropDups: true
      }
    },
    password: {
      type: String,
      required: true
    }
  };
}

/**
 * Extends the User schema with the given fields.
 *
 * @param {object} fields - Fields in Mongoose schema format
 */
Auth.prototype.extendUser = function extendUser(fields) {
  if (fields.hasOwnProperty("username") || fields.hasOwnProperty("password")) {
    throw new Error("Cannot redefine username or password user fields!");
  }
  this._userSchema = xtend(fields, this._userSchema);
};

/**
 * Creates the user model from the schema. Only call this once. Internal use only.
 */
Auth.prototype._createUserModel = function _createUserModel(fields) {
  return mongoose.model("User", this._userSchema);
};
<commit_msg>Add login/logout<commit_after>"use strict";

var mongoose = require("mongoose");
var passport = require("passport");
var xtend = require("xtend");

module.exports = Auth;

function Auth() {
  // These fields are required for the user schema
  this._userSchema = {
    username: {
      type: String,
      required: true,
      index: {
        unique: true,
        dropDups: true
      }
    },
    password: {
      type: String,
      required: true
    }
  };
}

/**
 * Extends the User schema with the given fields.
 *
 * @param {object} fields - Fields in Mongoose schema format
 */
Auth.prototype.extendUser = function extendUser(fields) {
  if (fields.hasOwnProperty("username") || fields.hasOwnProperty("password")) {
    throw new Error("Cannot redefine username or password user fields!");
  }
  this._userSchema = xtend(fields, this._userSchema);
};

/**
 * Sets up the auth
 */
Auth.prototype.setup = function setup(app, root) {
  var User = mongoose.model("User", new mongoose.Schema(this._userSchema));

  app.post(root + "/login", passport.authenticate("local", function(req, res) {
    res.json({ username: req.user.username });
  }));

  app.get(root + "/logout", function (req, res) {
    req.logout();
    res.send(300);
  });
}
<|endoftext|>


<commit_before>(function () {
  'use strict';

  function isArray(v) {
    return Array.isArray(v);
  }

  function isObject(v) {
    return !!v && typeof v === 'object';
  }

  function identity(v) {
    return v;
  }

  function comparator(getPriority) {
    return function(a, b) {
      return getPriority(a) - getPriority(b);
    };
  }

  function order(comparator, compare) {
    return function(a, b) {
      return comparator(a, b) * compare;
    };
  }

  function Pqueue(data, opts) {
    var initialData = isArray(data) && data || [];
    var opts = !isArray(data) && isObject(data) && data || isObject(opts) && opts || {};
    var direction = opts.min && -1 || 1;
    var getPriority = opts.getPriority || identity;

    this._comparator = order(opts.comparator || comparator(getPriority), direction);
    this._q = [];

    this.pushArray(initialData);
  }

  Pqueue.prototype.push = function push(val) {
    var parent, length;
    this._q.push(val);
    length = this._q.length - 1;

    // Starting from the end
    for (var i = length; i; i = parent) {
      parent = (i - 1) >> 1;

      // Break if the parent is of higher priority
      if (this._comparator(this._q[parent], val) > 0) break;

      // Otherwise move the parent down
      this._q[i] = this._q[parent];
    }

    this._q[i] = val;
    return length;
  };

  Pqueue.prototype.pop = function pop() {
    var result = this._q[0];
    var last = this._q.pop();
    var length = this._q.length;
    var swapChild, otherChild;

    if (length > 0) {
      for (var i = 0; ; i = swapChild) {
        // First child swap position
        swapChild = i << 1 | 1;

        // Break if we reached the end
        if (swapChild >= length) break;

        // Second child position
        otherChild = swapChild + 1;

        // Set swapChild to otherChild if otherChild is of higher priority
        if ((otherChild < length) && this._comparator(this._q[otherChild], this._q[swapChild]) > 0)
          swapChild = otherChild;

        // Break if the last element is of higher priority than its child
        if (this._comparator(last, this._q[swapChild]) > 0) break;

        // Move the child up
        this._q[i] = this._q[swapChild];
      }

      this._q[i] = last;
    }

    return result;
  };

  Pqueue.prototype.peek = function peek() {
    return this._q[0];
  };

  Pqueue.prototype.clear = function clear() {
    this._q.length = 0;
  };

  Pqueue.prototype.pushArray = function pushArray(arr) {
    var arrLen = arr.length;

    for (var i = 0; i < arrLen; i++) {
      this.push(arr[i]);
    }
  };

  Object.defineProperty(Pqueue.prototype, 'length', {
    get: function() {
      return this._q.length;
    },
    set: function() {
      throw new RangeError('');
    }
  });

  if (typeof module !== 'undefined' && module !== null && module.exports !== 'undefined' && module.exports !== null) {
    module.exports = Pqueue;
  } else if (this) {
    this.Pqueue = Pqueue;
  }

  return Pqueue;
}).call(this);
<commit_msg>Tidy up<commit_after>(function () {
  'use strict';

  function isArray(v) {
    return Array.isArray(v);
  }

  function isObject(v) {
    return !!v && typeof v === 'object';
  }

  function identity(v) {
    return v;
  }

  function comparator(getPriority) {
    return function(a, b) {
      return getPriority(a) - getPriority(b);
    };
  }

  function order(comparator, compare) {
    return function(a, b) {
      return comparator(a, b) * compare;
    };
  }

  function Pqueue(data, opts) {
    var initialData = isArray(data) && data || [];
    var opts = !isArray(data) && isObject(data) && data || isObject(opts) && opts || {};
    var direction = opts.min && -1 || 1;
    var getPriority = opts.getPriority || identity;

    this._comparator = order(opts.comparator || comparator(getPriority), direction);
    this._q = [];

    this.pushArray(initialData);
  }

  Pqueue.prototype.push = function(val) {
    var parent, length;
    this._q.push(val);
    length = this._q.length - 1;

    // Starting from the end
    for (var i = length; i; i = parent) {
      parent = (i - 1) >> 1;

      // Break if the parent is of higher priority
      if (this._comparator(this._q[parent], val) > 0) break;

      // Otherwise move the parent down
      this._q[i] = this._q[parent];
    }

    this._q[i] = val;
    return length;
  };

  Pqueue.prototype.pop = function() {
    var result = this._q[0];
    var last = this._q.pop();
    var length = this._q.length;
    var swapChild, otherChild;

    if (length > 0) {
      for (var i = 0; ; i = swapChild) {
        // First child swap position
        swapChild = i << 1 | 1;

        // Break if we reached the end
        if (swapChild >= length) break;

        // Second child position
        otherChild = swapChild + 1;

        // Set swapChild to otherChild if otherChild is of higher priority
        if ((otherChild < length) && this._comparator(this._q[otherChild], this._q[swapChild]) > 0)
          swapChild = otherChild;

        // Break if the last element is of higher priority than its child
        if (this._comparator(last, this._q[swapChild]) > 0) break;

        // Move the child up
        this._q[i] = this._q[swapChild];
      }

      this._q[i] = last;
    }

    return result;
  };

  Pqueue.prototype.peek = function() {
    return this._q[0];
  };

  Pqueue.prototype.clear = function() {
    this._q.length = 0;
  };

  Pqueue.prototype.pushArray = function(arr) {
    var arrLen = arr.length;

    for (var i = 0; i < arrLen; i++) {
      this.push(arr[i]);
    }
  };

  Object.defineProperty(Pqueue.prototype, 'length', {
    get: function() {
      return this._q.length;
    },
    set: function() {
      throw new RangeError('');
    }
  });

  if (typeof module !== 'undefined' && module !== null && module.exports !== 'undefined' && module.exports !== null) {
    module.exports = Pqueue;
  } else if (this) {
    this.Pqueue = Pqueue;
  }

  return Pqueue;
}).call(this);
<|endoftext|>


<commit_before>/**
 * Jasmine 2.0 standalone `boot.js` modified for Karma.
 * This file is registered in `index.js`. This version
 * does not include `HtmlReporter` setup.
 */
(function(){

  /**
   * Require Jasmine's core files. Specifically, this requires and
   * attaches all of Jasmine's code to the `jasmine` reference.
   */
  window.jasmine = jasmineRequire.core(jasmineRequire);

  /**
   * Create the Jasmine environment. This is used to run all specs
   * in a project.
   */
  var env = jasmine.getEnv();

  var focusedSuites = [];
  var focusedSpecs  = [];
  var insideFocusedSuite = false;

  var focuseSpec = function(env, description, body) {
    var spec = env.it(description, body);
    focusedSpecs.push(spec.id);
    return spec;
  };

  var focuseSuite = function(env, description, body) {
    if (insideFocusedSuite) {
      return env.describe(description, body);
    }

    insideFocusedSuite = true;
    var suite = env.describe(description, body);
    insideFocusedSuite = false
    focusedSuites.push(suite.id);
    return suite;
  };

  /**
   * Build up the functions that will be exposed as the Jasmine
   * public interface.
   */
  var jasmineInterface = {
    describe: function(description, specDefinitions) {
      return env.describe(description, specDefinitions);
    },

    xdescribe: function(description, specDefinitions) {
      return env.xdescribe(description, specDefinitions);
    },

    ddescribe: function(description, specDefinitions) {
      return focuseSuite(env, description, specDefinitions);
    },

    it: function(desc, func) {
      return env.it(desc, func);
    },

    xit: function(desc, func) {
      return env.xit(desc, func);
    },

    iit: function(desc, func) {
      return focuseSpec(env, desc, func);
    },

    beforeEach: function(beforeEachFunction) {
      return env.beforeEach(beforeEachFunction);
    },

    afterEach: function(afterEachFunction) {
      return env.afterEach(afterEachFunction);
    },

    expect: function(actual) {
      return env.expect(actual);
    },

    pending: function() {
      return env.pending();
    },

    spyOn: function(obj, methodName) {
      return env.spyOn(obj, methodName);
    },

    jsApiReporter: new jasmine.JsApiReporter({
      timer: new jasmine.Timer()
    })
  };

  /**
   * Add all of the Jasmine global/public interface to the proper
   * global, so a project can use the public interface directly.
   * For example, calling `describe` in specs instead of
   * `jasmine.getEnv().describe`.
   */
  for (var property in jasmineInterface) {
    if (jasmineInterface.hasOwnProperty(property)) {
      window[property] = jasmineInterface[property];
    }
  }

  env.executeFiltered = function() {
    if (focusedSpecs.length) {
      env.execute(focusedSpecs);
    } else if (focusedSuites.length) {
      env.execute(focusedSuites);
    } else {
      env.execute();
    }
  };

  /**
   * Expose the interface for adding custom equality testers.
   */
  jasmine.addCustomEqualityTester = function(tester) {
    env.addCustomEqualityTester(tester);
  };

  /**
   * Expose the interface for adding custom expectation matchers
   */
  jasmine.addMatchers = function(matchers) {
    return env.addMatchers(matchers);
  };

  /**
   * Expose the mock interface for the JavaScript timeout functions
   */
  jasmine.clock = function() {
    return env.clock;
  };

})();
<commit_msg>fix: support timeout mocks on ie8<commit_after>/**
 * Jasmine 2.0 standalone `boot.js` modified for Karma.
 * This file is registered in `index.js`. This version
 * does not include `HtmlReporter` setup.
 */
(function(){

  /**
   * Require Jasmine's core files. Specifically, this requires and
   * attaches all of Jasmine's code to the `jasmine` reference.
   */
  window.jasmine = jasmineRequire.core(jasmineRequire);

  /**
   * Create the Jasmine environment. This is used to run all specs
   * in a project.
   */
  var env = jasmine.getEnv();

  var focusedSuites = [];
  var focusedSpecs  = [];
  var insideFocusedSuite = false;

  var focuseSpec = function(env, description, body) {
    var spec = env.it(description, body);
    focusedSpecs.push(spec.id);
    return spec;
  };

  var focuseSuite = function(env, description, body) {
    if (insideFocusedSuite) {
      return env.describe(description, body);
    }

    insideFocusedSuite = true;
    var suite = env.describe(description, body);
    insideFocusedSuite = false
    focusedSuites.push(suite.id);
    return suite;
  };

  /**
   * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require
   * this hack.
   */
  window.setTimeout = window.setTimeout;
  window.setInterval = window.setInterval;
  window.clearTimeout = window.clearTimeout;
  window.clearInterval = window.clearInterval;

  /**
   * Build up the functions that will be exposed as the Jasmine
   * public interface.
   */
  var jasmineInterface = {
    describe: function(description, specDefinitions) {
      return env.describe(description, specDefinitions);
    },

    xdescribe: function(description, specDefinitions) {
      return env.xdescribe(description, specDefinitions);
    },

    ddescribe: function(description, specDefinitions) {
      return focuseSuite(env, description, specDefinitions);
    },

    it: function(desc, func) {
      return env.it(desc, func);
    },

    xit: function(desc, func) {
      return env.xit(desc, func);
    },

    iit: function(desc, func) {
      return focuseSpec(env, desc, func);
    },

    beforeEach: function(beforeEachFunction) {
      return env.beforeEach(beforeEachFunction);
    },

    afterEach: function(afterEachFunction) {
      return env.afterEach(afterEachFunction);
    },

    expect: function(actual) {
      return env.expect(actual);
    },

    pending: function() {
      return env.pending();
    },

    spyOn: function(obj, methodName) {
      return env.spyOn(obj, methodName);
    },

    jsApiReporter: new jasmine.JsApiReporter({
      timer: new jasmine.Timer()
    })
  };

  /**
   * Add all of the Jasmine global/public interface to the proper
   * global, so a project can use the public interface directly.
   * For example, calling `describe` in specs instead of
   * `jasmine.getEnv().describe`.
   */
  for (var property in jasmineInterface) {
    if (jasmineInterface.hasOwnProperty(property)) {
      window[property] = jasmineInterface[property];
    }
  }

  env.executeFiltered = function() {
    if (focusedSpecs.length) {
      env.execute(focusedSpecs);
    } else if (focusedSuites.length) {
      env.execute(focusedSuites);
    } else {
      env.execute();
    }
  };

  /**
   * Expose the interface for adding custom equality testers.
   */
  jasmine.addCustomEqualityTester = function(tester) {
    env.addCustomEqualityTester(tester);
  };

  /**
   * Expose the interface for adding custom expectation matchers
   */
  jasmine.addMatchers = function(matchers) {
    return env.addMatchers(matchers);
  };

  /**
   * Expose the mock interface for the JavaScript timeout functions
   */
  jasmine.clock = function() {
    return env.clock;
  };

})();
<|endoftext|>


<commit_before>var assert = require('assert');
var deepExtend = require('deep-extend');
var dot = require('dot-object');
var fs = require('fs');
var path = require('path');
var util = require('util');
var yamlJs = require('yaml-js');

// Replaces aliases by the block content of the anchors
function replaceAliases(yamlString) {
  var yamlLines = yamlString.split("\n");
  var newYamlLines = [];

  // Strip empty lines and comments
  yamlLines.forEach(function (line) {
    if (line.length > 0 && line.trim().indexOf("#") === -1) {
      newYamlLines.push(line);
    }
  });

  var anchors = [];

  // Find anchors, with their indentation and block content
  newYamlLines.forEach(function (line, index) {
    if (line.indexOf("&") > -1) {
      var anchorName = line.match("&(.*)")[1];

      var me = anchors[anchorName] = {
        indent: line.search(/\S|$/),
        block: []
      };

      // Store the block content
      for (var i = index + 1; i < newYamlLines.length; i++) {
        // when encountering an equal or lower indentation, the block ends
        if (newYamlLines[i].search(/\S|$/) <= me.indent) {
          break;
        }
        me.block.push(newYamlLines[i]);
      }
    }
  });

  var finalYamlLines = [];

  // Find aliases and add anchor content
  newYamlLines.forEach(function (line, index) {
    if (line.trim().indexOf("<<: *") == 0) {
      var aliasName = line.trim().replace("<<: *", "");

      /* istanbul ignore else */
      if (anchors[aliasName]) {
        var indentDiff = line.search(/\S|$/) - anchors[aliasName].indent - 2;

        anchors[aliasName].block.forEach(function (blockLine) {
          var str;
          // Shift indentations of the block content
          if (indentDiff > 0) {
            str = new Array(indentDiff + 1).join(' ') + blockLine;
          } else {
            str = blockLine;
            for (var i = 0; i > indentDiff; i--) {
              str = str.replace('  ', ' ');
            }
          }

          finalYamlLines.push(str);
        });
      }
    } else {
      finalYamlLines.push(line);
    }
  });

  return finalYamlLines;
}

/** Write out the entire hierarchy in YAML
 * a:
 *  b:
 *   c:
 *
 * Becomes:
 *
 * a:
 * a.b:
 * a.b.c:
 *
 * @param yamlLines
 */
function blowUpHierarchy(yamlLines) {
  var key = "";
  var indent = 0;
  var currentIndent = 0;
  var indentSteps = [];
  var postLines = [];

  for (var index = 0; index < yamlLines.length; index++) {
    var line = yamlLines[index];
    // Skip empty lines and comments
    if (line.trim().length === 0 || line.trim().indexOf("#") > -1) {
      continue;
    }

    var match = line.trim().match(".*:");
    currentIndent = yamlLines[index].search(/\S|$/);

    // Down the tree
    if (match && (currentIndent > indent)) {
      if (currentIndent !== 0) {
        key += '.';
      }
      key += match[0].replace(/:.*/, "");
      if ((currentIndent - indent) > 0) {
        indentSteps.push(currentIndent - indent);
      }
    }

    // Up the tree or peer property
    if (match && (currentIndent <= indent)) {
      var keyArr = key.split('.');
      keyArr.pop();

      var steps = 0;
      // Get the number of steps to travel up the hierarchy tree
      while (indent > currentIndent) {
        indent -= indentSteps.pop();
        keyArr.pop();
        steps++;
      }

      key = keyArr.join('.');
      if (currentIndent !== 0) {
        key += '.';
      }
      key += match[0].replace(/:.*/, "");
    }

    // We're in a list, simply push the content
    if (line.trim().indexOf("-") === 0) {
      while(yamlLines[index].trim().indexOf("-") === 0 || yamlLines[index].search(/\S|$/) > currentIndent) {
        postLines.push(yamlLines[index]);
        index++;
      }
    } else if (line.trim().match(/.*:/)) {
      postLines.push(line.replace(line.split(":")[0], key));
    } else {
      postLines.push(line);
    }

    indent = currentIndent;
  }
  return postLines;
}

function parse(yamlLines) {
  var yaml = yamlJs.load(yamlLines.join('\n'));
  var result = [{}];

  Object.keys(yaml).forEach(function (key) {
    var value = yaml[key];

    if (value) {
      result.push(dot.str(key, value, {}));
    }
  });

  deepExtend.apply(null, result);
  return result[0];
}

function camlize(options) {
  if (!options) {
    return;
  }
  // Default dir is the current working dir
  options.dir = options.dir || path.join(process.cwd());
  var yamlString = "";

  options.files.forEach(function (fileName) {
    if (path.isAbsolute(fileName)) {
      yamlString += fs.readFileSync(fileName + ".yml", 'UTF8');
    } else {
      yamlString += fs.readFileSync(path.join(options.dir, fileName + ".yml"), 'UTF8');
    }
  });

  return parse(
    blowUpHierarchy(
      replaceAliases(yamlString)
    )
  );
}

module.exports = {
  camlize: camlize,
  blowUpHierarchy: blowUpHierarchy,
  parse: parse,
  replaceAliases: replaceAliases
};
<commit_msg>Also allow false values...<commit_after>var assert = require('assert');
var deepExtend = require('deep-extend');
var dot = require('dot-object');
var fs = require('fs');
var path = require('path');
var util = require('util');
var yamlJs = require('yaml-js');

// Replaces aliases by the block content of the anchors
function replaceAliases(yamlString) {
  var yamlLines = yamlString.split("\n");
  var newYamlLines = [];

  // Strip empty lines and comments
  yamlLines.forEach(function (line) {
    if (line.length > 0 && line.trim().indexOf("#") === -1) {
      newYamlLines.push(line);
    }
  });

  var anchors = [];

  // Find anchors, with their indentation and block content
  newYamlLines.forEach(function (line, index) {
    if (line.indexOf("&") > -1) {
      var anchorName = line.match("&(.*)")[1];

      var me = anchors[anchorName] = {
        indent: line.search(/\S|$/),
        block: []
      };

      // Store the block content
      for (var i = index + 1; i < newYamlLines.length; i++) {
        // when encountering an equal or lower indentation, the block ends
        if (newYamlLines[i].search(/\S|$/) <= me.indent) {
          break;
        }
        me.block.push(newYamlLines[i]);
      }
    }
  });

  var finalYamlLines = [];

  // Find aliases and add anchor content
  newYamlLines.forEach(function (line, index) {
    if (line.trim().indexOf("<<: *") == 0) {
      var aliasName = line.trim().replace("<<: *", "");

      /* istanbul ignore else */
      if (anchors[aliasName]) {
        var indentDiff = line.search(/\S|$/) - anchors[aliasName].indent - 2;

        anchors[aliasName].block.forEach(function (blockLine) {
          var str;
          // Shift indentations of the block content
          if (indentDiff > 0) {
            str = new Array(indentDiff + 1).join(' ') + blockLine;
          } else {
            str = blockLine;
            for (var i = 0; i > indentDiff; i--) {
              str = str.replace('  ', ' ');
            }
          }

          finalYamlLines.push(str);
        });
      }
    } else {
      finalYamlLines.push(line);
    }
  });

  return finalYamlLines;
}

/** Write out the entire hierarchy in YAML
 * a:
 *  b:
 *   c:
 *
 * Becomes:
 *
 * a:
 * a.b:
 * a.b.c:
 *
 * @param yamlLines
 */
function blowUpHierarchy(yamlLines) {
  var key = "";
  var indent = 0;
  var currentIndent = 0;
  var indentSteps = [];
  var postLines = [];

  for (var index = 0; index < yamlLines.length; index++) {
    var line = yamlLines[index];
    // Skip empty lines and comments
    if (line.trim().length === 0 || line.trim().indexOf("#") > -1) {
      continue;
    }

    var match = line.trim().match(".*:");
    currentIndent = yamlLines[index].search(/\S|$/);

    // Down the tree
    if (match && (currentIndent > indent)) {
      if (currentIndent !== 0) {
        key += '.';
      }
      key += match[0].replace(/:.*/, "");
      if ((currentIndent - indent) > 0) {
        indentSteps.push(currentIndent - indent);
      }
    }

    // Up the tree or peer property
    if (match && (currentIndent <= indent)) {
      var keyArr = key.split('.');
      keyArr.pop();

      var steps = 0;
      // Get the number of steps to travel up the hierarchy tree
      while (indent > currentIndent) {
        indent -= indentSteps.pop();
        keyArr.pop();
        steps++;
      }

      key = keyArr.join('.');
      if (currentIndent !== 0) {
        key += '.';
      }
      key += match[0].replace(/:.*/, "");
    }

    // We're in a list, simply push the content
    if (line.trim().indexOf("-") === 0) {
      while(yamlLines[index].trim().indexOf("-") === 0 || yamlLines[index].search(/\S|$/) > currentIndent) {
        postLines.push(yamlLines[index]);
        index++;
      }
    } else if (line.trim().match(/.*:/)) {
      postLines.push(line.replace(line.split(":")[0], key));
    } else {
      postLines.push(line);
    }

    indent = currentIndent;
  }
  return postLines;
}

function parse(yamlLines) {
  var yaml = yamlJs.load(yamlLines.join('\n'));
  var result = [{}];

  Object.keys(yaml).forEach(function (key) {
    var value = yaml[key];

    if (value !== null) {
      result.push(dot.str(key, value, {}));
    }
  });

  deepExtend.apply(null, result);
  return result[0];
}

function camlize(options) {
  if (!options) {
    return;
  }
  // Default dir is the current working dir
  options.dir = options.dir || path.join(process.cwd());
  var yamlString = "";

  options.files.forEach(function (fileName) {
    if (path.isAbsolute(fileName)) {
      yamlString += fs.readFileSync(fileName + ".yml", 'UTF8');
    } else {
      yamlString += fs.readFileSync(path.join(options.dir, fileName + ".yml"), 'UTF8');
    }
  });

  return parse(
    blowUpHierarchy(
      replaceAliases(yamlString)
    )
  );
}

module.exports = {
  camlize: camlize,
  blowUpHierarchy: blowUpHierarchy,
  parse: parse,
  replaceAliases: replaceAliases
};
<|endoftext|>