<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import '../App.css'
import * as firebase from 'firebase'
import TriCheckbox from '../components/TriCheckbox'
import {
  Button,
  Form,
  FormGroup,
  FormControl,
  ControlLabel,
  Col,
  ListGroup,
  ListGroupItem,
  InputGroup
} from 'react-bootstrap'
import {
  SortableContainer,
  SortableElement,
  arrayMove
} from 'react-sortable-hoc'

class Item extends Component {
  static propTypes = {
    item: PropTypes.object.isRequired
  }
  removeItem = () => {
    const shopListRef = firebase.database().ref().child('shopList')
    shopListRef.child(this.props.item.key).remove()
  }
  onCheckItemChange = (event) => {
    const checked = event.target.checked
    var updates = {}
    updates['shopList/' + this.props.item.key + '/ticked'] = checked
    return firebase.database().ref().update(updates)
  }
  render () {
    return (
      <ListGroupItem>

          <TriCheckbox onChange={this.onCheckItemChange}
            checked={this.props.item.ticked} />
          {' '}
          {this.props.item.name}

          <Button bsSize='xsmall' bsStyle='danger'
            onClick={this.removeItem}>
            x
          </Button>

      </ListGroupItem>
    )
  }
}

const SortableItem = SortableElement(Item)

class Items extends Component {
  static propTypes = {
    items: PropTypes.array.isRequired
  }
  render () {
    return (

        <ListGroup>
          {this.props.items.map((value, index) => (
            <SortableItem key={`item-${index}`} index={index}
              value={value.name} item={value} />
          ))}
        </ListGroup>

    )
  }
}

const SortableList = SortableContainer(Items)

class RecipeList extends Component {
  constructor () {
    super()
    this.state = {
      items: undefined,
      itemName: ''
    }
  }
  componentDidMount () {
    const rootRef = firebase.database().ref()
    const itemsRef = rootRef.child('shopList')
    itemsRef.on('value', snap => {
      this.setState({
        items: Object.values(snap.val()).map((item) => {
          return item
        })
      })
    }, function (errorObject) {
      console.error('The read failed: ' + errorObject.code)
    })
  }
  onSortEnd = ({oldIndex, newIndex}) => {
    this.setState({
      items: arrayMove(this.state.items, oldIndex, newIndex)
    })
  }
  sortableList = (items) => {
    return items
    ? <SortableList items={items} onSortEnd={this.onSortEnd} />
    : 'Nothing here but us chickens'
  }

  addItem = (e) => {
    e.preventDefault()
    // Get a key for a new Post.
    var newItemKey = firebase.database().ref().child('shopList').push().key
    // An item entry.
    const itemData = {
      key: newItemKey,
      name: this.state.itemName,
      cost: 1,
      ticked: false
    }
    var updates = {}
    updates['/shopList/' + newItemKey] = itemData
    return firebase.database().ref().update(updates)
  }

  removeItem = (item) => {
    const shopListRef = firebase.database().ref().child('shopList')
    shopListRef.child(item.key).remove()
  }

  handleChange = (event) => {
    this.setState({
      itemName: event.target.value
    })
  }

  render () {
    return (

        <Col xs={3} md={3} />
        <Col xs={12} md={6}>
          {this.sortableList(this.state.items)}
          <Form inline onSubmit={this.addItem}>
            <FormGroup controlId='formBasicText'>
              <ControlLabel>Add an Item</ControlLabel>
              {' '}
              <InputGroup>
                <FormControl
                  type='text'
                  value={this.state.itemName}
                  placeholder='Enter item name'
                  onChange={this.handleChange}
                />
                <InputGroup.Button>
                  <Button type='submit' bsStyle='success'>+</Button>
                </InputGroup.Button>
              </InputGroup>
            </FormGroup>
          </Form>
        </Col>
        <Col xs={3} md={3} />

    )
  }
}

export default RecipeList
<commit_msg>feat: sort checked items<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import '../App.css'
import * as firebase from 'firebase'
import TriCheckbox from '../components/TriCheckbox'
import {
  Button,
  Form,
  FormGroup,
  FormControl,
  ControlLabel,
  Col,
  ListGroup,
  ListGroupItem,
  InputGroup
} from 'react-bootstrap'
import {
  SortableContainer,
  // SortableElement,
  arrayMove
} from 'react-sortable-hoc'

class Item extends Component {
  static propTypes = {
    item: PropTypes.object.isRequired
  }
  removeItem = () => {
    const shopListRef = firebase.database().ref().child('shopList')
    shopListRef.child(this.props.item.key).remove()
  }
  onCheckItemChange = (event) => {
    const checked = event.target.checked
    var updates = {}
    updates['shopList/' + this.props.item.key + '/ticked'] = checked
    return firebase.database().ref().update(updates)
  }
  render () {
    return (
      <ListGroupItem>

            <TriCheckbox onChange={this.onCheckItemChange}
              checked={this.props.item.ticked} />
            {' ' + this.props.item.name}

          <Button bsSize='xsmall' bsStyle='danger'
            onClick={this.removeItem}>
            x
          </Button>

      </ListGroupItem>
    )
  }
}

// Disabling draggable sorting
// const SortableItem = SortableElement(Item)

class Items extends Component {
  static propTypes = {
    items: PropTypes.array.isRequired
  }
  render () {
    const checkedItems = this.props.items.filter(function (item) {
      return item.ticked === true
    })
    const unCheckedItems = this.props.items.filter(function (item) {
      return item.ticked === false
    })
    return (

        <ListGroup>
          {unCheckedItems.map((value, index) => (
            <Item key={`item-${index}`} index={index}
              value={value.name} item={value} />
          ))}
          {checkedItems.map((value, index) => (
            <Item key={`item-${index}`} index={index}
              value={value.name} item={value} />
          ))}
        </ListGroup>

    )
  }
}

const SortableList = SortableContainer(Items)

class RecipeList extends Component {
  constructor () {
    super()
    this.state = {
      items: undefined,
      itemName: ''
    }
  }
  componentDidMount () {
    const rootRef = firebase.database().ref()
    const itemsRef = rootRef.child('shopList')
    itemsRef.on('value', snap => {
      this.setState({
        items: Object.values(snap.val()).map((item) => {
          return item
        })
      })
    }, function (errorObject) {
      console.error('The read failed: ' + errorObject.code)
    })
  }
  onSortEnd = ({oldIndex, newIndex}) => {
    this.setState({
      items: arrayMove(this.state.items, oldIndex, newIndex)
    })
  }
  sortableList = (items) => {
    return items
    ? <SortableList items={items} onSortEnd={this.onSortEnd} />
    : 'Nothing here but us chickens'
  }

  addItem = (e) => {
    // e.preventDefault()
    // Get a key for a new Post.
    var newItemKey = firebase.database().ref().child('shopList').push().key
    // An item entry.
    const itemData = {
      key: newItemKey,
      name: this.state.itemName,
      cost: 1,
      ticked: false
    }
    var updates = {}
    updates['/shopList/' + newItemKey] = itemData
    return firebase.database().ref().update(updates)
  }

  removeItem = (item) => {
    const shopListRef = firebase.database().ref().child('shopList')
    shopListRef.child(item.key).remove()
  }

  handleChange = (event) => {
    this.setState({
      itemName: event.target.value
    })
  }

  render () {
    return (

        <Col xs={3} md={3} />
        <Col xs={12} md={6}>
          {this.sortableList(this.state.items)}
          <Form inline onSubmit={this.addItem}>
            <FormGroup controlId='formBasicText'>
              <ControlLabel>Add an Item</ControlLabel>
              {' '}
              <InputGroup>
                <FormControl
                  type='text'
                  value={this.state.itemName}
                  placeholder='Enter item name'
                  onChange={this.handleChange}
                />
                <InputGroup.Button>
                  <Button type='submit' bsStyle='success'>+</Button>
                </InputGroup.Button>
              </InputGroup>
            </FormGroup>
          </Form>
        </Col>
        <Col xs={3} md={3} />

    )
  }
}

export default RecipeList
<|endoftext|>


<commit_before>import mongoose from 'mongoose';
import Promise from 'bluebird';
import debug from 'debug';

import { createCommand } from '../sockets';
import { GenericError } from '../errors';
import { fetchMedia } from './search';

const ObjectId = mongoose.Types.ObjectId;
const log = debug('uwave:api:v1:playlists');

const addMedia = function addMedia(sourceType, sourceID, keys, Media) {
  return fetchMedia(sourceType, sourceID, keys)
  .then(media => {
    return new Media(media).save();
  });
};

const toPlaylistResponse = function toPlaylistResponse(model) {
  return {
    '_id': model.id,
    'name': model.name,
    'author': model.author,
    'created': model.created,
    'description': model.description,
    'shared': model.shared,
    'nsfw': model.nsfw,
    'size': model.media.length
  };
};

export const getPlaylists = function getPlaylists(page, limit, id, mongo) {
  const Playlist = mongo.model('Playlist');

  const _page = (isNaN(page) ? 0 : page);
  const _limit = (isNaN(limit) ? 50 : Math.ceil(limit, 50));

  return Playlist.find({'author': id})
    .setOptions({ 'limit': _limit, 'skip': _limit * _page })
    .exec()
    .map(toPlaylistResponse);
};

export const createPlaylist = function createPlaylist(data, mediaArray, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  const playlist = new Playlist(data);

  return playlist.validate()
  .then(() => {
    let pending = mediaArray.length;
    const cb = (err, media) => {
      if (err) {
        playlist.remove();
        throw err;
      }

      pending--;
      playlist.media.push(media.id);
      if (!pending) return playlist.save();
    };

    if (!pending) return playlist.save();

    for (let i = 0, l = mediaArray.length; i < l; i++) {
      new PlaylistItem(mediaArray[i]).save();
    }
  });
};

export const getPlaylist = function getPlaylist(page, limit, id, playlistID, populate, mongo) {
  const Playlist = mongo.model('Playlist');
  const _page = (isNaN(page) ? 0 : page);
  const _limit = (isNaN(limit) ? 100 : Math.min(limit, 100));
  const _slice = { 'media' : { '$slice': [_limit * _page, _limit ] } };

  return Playlist.findOne(ObjectId(playlistID), (populate ? _slice : {}))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${ID} not found`);
    if (id !== playlist.author.toString() && !playlist.shared) {
      throw new GenericError(403, 'this playlist is private');
    }

    if (populate) {
      return playlist.populate('media').execPopulate()
      .then(_playlist => {
        return Playlist.populate(_playlist, { 'path': 'media.media', 'model': 'Media' });
      });
    } else {
      return toPlaylistResponse(playlist);
    }
  });
};

export const deletePlaylist = function deletePlaylist(id, playlistID, uwave) {
  const Playlist = uwave.mongo.model('Playlist');
  let _active = null;

  return uwave.redis.get(`playlist:${id}`)
  .then(active => {
    _active = active;
    return Playlist.findOne(ObjectId(playlistID));
  })
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t delete the playlist of another user');
    }
    if (_active && _active === playlist.id) {
      throw new GenericError(403, 'you can\'t delete an active playlist');
    }

    return playlist.remove();
  });
};

export const renamePlaylist = function renamePlaylist(id, playlistID, name, mongo) {
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t rename the playlist of another user');
    }

    playlist.name = name;
    return playlist.save();
  });
};

export const sharePlaylist = function sharePlaylist(id, playlistID, shared, mongo) {
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t share the playlist of another user');
    }

    playlist.shared = shared;
    return playlist.save();
  });
};

export const movePlaylistItems = function movePlaylistItems(id, playlistID, after, items, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');
  let pos = -1;

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t edit the playlist of another user');
    }

    const _items = [];

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      const _id = playlist.media[i].toString();

      for (let j = items.length - 1; j >= 0; j--) {
        if (_id === items[j]) {
          _items.push(playlist.media.splice(i, 1)[0]);
          items.splice(j, 1);
          break;
        }
      }
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].toString() === after) {
        pos = i;
        break;
      }
    }

    playlist.media.splice(pos + 1, 0, ..._items);
    return playlist.save();
  });
};

export const activatePlaylist = function activatePlaylist(id, playlistID, uwave) {
  const Playlist = uwave.mongo.model('Playlist');
  return Playlist.findOne(ObjectId(playlistID)).populate('author')
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.id && playlist.shared) {
      throw new GenericError(403, `${playlist.author.username} has made ${playlist.name} private`);
    }

    uwave.redis.set(`playlist:${id}`, playlist.id);
    return uwave.redis.get(`playlist:${id}`);
  });
};

export const createPlaylistItems = function createPlaylistItems(id, playlistID, items, uwave) {
  const PlaylistItem = uwave.mongo.model('PlaylistItem');
  const Playlist = uwave.mongo.model('Playlist');
  const Media = uwave.mongo.model('Media');

  const _items = [];

  const _addMedia = function(sourceType, sourceID) {
    let _playlistItem = null;

    return Media.findOne({ 'sourceType': sourceType, 'sourceID': sourceID })
    .then(media => {
      if (!media) {
        return addMedia(sourceType, sourceID, uwave.keys, Media);
      } else {
        return media;
      }
    })
    .then(media => {
      _playlistItem = new PlaylistItem({
        'media': media,
        'artist': media.artist,
        'title': media.title
      });

      return _playlistItem.save();
    })
    .then(playlistItem => {
      if (!playlistItem) throw new Error('couldn\'t save media');

      return playlistItem;
    }, e => {
      if (_playlistItem) _playlistItem.remove();
      throw e;
    });
  };

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (playlist.author.toString() !== id) throw new GenericError(403, 'you can\'t edit the playlist of another user');

    for (let i = 0, l = items.length; i < l; i++) {
      if (typeof items[i] !== 'object') continue;
      if (
        typeof items[i].sourceType !== 'string' ||
        (typeof items[i].sourceID !== 'string' && typeof items[i].sourceID !== 'number')
      ) continue;

      _items.push(_addMedia(items[i].sourceType, items[i].sourceID));
    }

    return Promise.all(_items)
    .then(playlistItems => {
      playlist.media = playlist.media.concat(playlistItems);

      return playlist.save()
      .then(() => {
        return {
          'added': playlistItems,
          'playlistSize': playlist.media.length
        };
      });
    });
  });
};

export const getPlaylistItem = function getPlaylistItem(id, playlistID, mediaID, mongo) {
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID)).populate('media')
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString() && playlist.shared) {
      throw new GenericError(403, 'this playlist is private');
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].id === mediaID) {
        return playlist.media[i];
      }
    }
    throw new GenericError(404, 'media not found');
  });
};

export const updatePlaylistItem = function updatePlaylistItem(id, playlistID, mediaID, metadata, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString() && playlist.shared) {
      throw new GenericError(403, 'playlist is private');
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].toString() === mediaID) {
        return PlaylistItem.findOneAndUpdate(playlist.media[i], metadata, { 'new': true });
      }
    }

    throw new GenericError(404, 'media not found');
  });
};

export const deletePlaylistItem = function deletePlaylistItem(id, playlistID, mediaID, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${id} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'playlist is private');
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].toString() === mediaID) {
        PlaylistItem.findOneAndRemove(playlist.media[i]);

        playlist.media.splice(i, 1);
        return playlist.save();
      }
    }

    throw new GenericError(404, 'media not found');
  });
};

export const copyPlaylistItem = function copyPlaylistItem(id, fromPlaylistID, mediaID, toPlaylistID, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  let _playlistItem = null;

  return Playlist.findOne(ObjectId(fromPlaylistID)).populate('media')
  .then(playlist => {
    if (!playlist) throw new GenericError(404, 'originating playlist not found');
    if (playlist.author.toString() !== id && !playlist.shared) throw new GenericError(403, 'originating playlist is private');

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlists.media[i].id === mediaID) {
        return PlaylistItem.findOne(ObjectId(mediaID), '-id');
      }
    }
  })
  .then(playlistItem => {
    return new PlaylistItem(playlistItem);
  })
  .then(playlistItem => {
    _playlistItem = playlistItem;
    return Playlist.findOne(ObjectId(toPlaylistID));
  })
  .then(playlist => {
    if (!playlist) throw new GenericError(404, 'playlist not found');
    if (!playlist.author.toString() !== id) throw new GenericError(403, 'you can\'t copy media to another user\'s playlist');
    playlist.media.push(_playlistItem.id);
    return playlist.save();
  });
}
<commit_msg>set end to media.duration at creation<commit_after>import mongoose from 'mongoose';
import Promise from 'bluebird';
import debug from 'debug';

import { createCommand } from '../sockets';
import { GenericError } from '../errors';
import { fetchMedia } from './search';

const ObjectId = mongoose.Types.ObjectId;
const log = debug('uwave:api:v1:playlists');

const addMedia = function addMedia(sourceType, sourceID, keys, Media) {
  return fetchMedia(sourceType, sourceID, keys)
  .then(media => {
    return new Media(media).save();
  });
};

const toPlaylistResponse = function toPlaylistResponse(model) {
  return {
    '_id': model.id,
    'name': model.name,
    'author': model.author,
    'created': model.created,
    'description': model.description,
    'shared': model.shared,
    'nsfw': model.nsfw,
    'size': model.media.length
  };
};

export const getPlaylists = function getPlaylists(page, limit, id, mongo) {
  const Playlist = mongo.model('Playlist');

  const _page = (isNaN(page) ? 0 : page);
  const _limit = (isNaN(limit) ? 50 : Math.ceil(limit, 50));

  return Playlist.find({'author': id})
    .setOptions({ 'limit': _limit, 'skip': _limit * _page })
    .exec()
    .map(toPlaylistResponse);
};

export const createPlaylist = function createPlaylist(data, mediaArray, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  const playlist = new Playlist(data);

  return playlist.validate()
  .then(() => {
    let pending = mediaArray.length;
    const cb = (err, media) => {
      if (err) {
        playlist.remove();
        throw err;
      }

      pending--;
      playlist.media.push(media.id);
      if (!pending) return playlist.save();
    };

    if (!pending) return playlist.save();

    for (let i = 0, l = mediaArray.length; i < l; i++) {
      new PlaylistItem(mediaArray[i]).save();
    }
  });
};

export const getPlaylist = function getPlaylist(page, limit, id, playlistID, populate, mongo) {
  const Playlist = mongo.model('Playlist');
  const _page = (isNaN(page) ? 0 : page);
  const _limit = (isNaN(limit) ? 100 : Math.min(limit, 100));
  const _slice = { 'media' : { '$slice': [_limit * _page, _limit ] } };

  return Playlist.findOne(ObjectId(playlistID), (populate ? _slice : {}))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${ID} not found`);
    if (id !== playlist.author.toString() && !playlist.shared) {
      throw new GenericError(403, 'this playlist is private');
    }

    if (populate) {
      return playlist.populate('media').execPopulate()
      .then(_playlist => {
        return Playlist.populate(_playlist, { 'path': 'media.media', 'model': 'Media' });
      });
    } else {
      return toPlaylistResponse(playlist);
    }
  });
};

export const deletePlaylist = function deletePlaylist(id, playlistID, uwave) {
  const Playlist = uwave.mongo.model('Playlist');
  let _active = null;

  return uwave.redis.get(`playlist:${id}`)
  .then(active => {
    _active = active;
    return Playlist.findOne(ObjectId(playlistID));
  })
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t delete the playlist of another user');
    }
    if (_active && _active === playlist.id) {
      throw new GenericError(403, 'you can\'t delete an active playlist');
    }

    return playlist.remove();
  });
};

export const renamePlaylist = function renamePlaylist(id, playlistID, name, mongo) {
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t rename the playlist of another user');
    }

    playlist.name = name;
    return playlist.save();
  });
};

export const sharePlaylist = function sharePlaylist(id, playlistID, shared, mongo) {
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t share the playlist of another user');
    }

    playlist.shared = shared;
    return playlist.save();
  });
};

export const movePlaylistItems = function movePlaylistItems(id, playlistID, after, items, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');
  let pos = -1;

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'you can\'t edit the playlist of another user');
    }

    const _items = [];

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      const _id = playlist.media[i].toString();

      for (let j = items.length - 1; j >= 0; j--) {
        if (_id === items[j]) {
          _items.push(playlist.media.splice(i, 1)[0]);
          items.splice(j, 1);
          break;
        }
      }
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].toString() === after) {
        pos = i;
        break;
      }
    }

    playlist.media.splice(pos + 1, 0, ..._items);
    return playlist.save();
  });
};

export const activatePlaylist = function activatePlaylist(id, playlistID, uwave) {
  const Playlist = uwave.mongo.model('Playlist');
  return Playlist.findOne(ObjectId(playlistID)).populate('author')
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.id && playlist.shared) {
      throw new GenericError(403, `${playlist.author.username} has made ${playlist.name} private`);
    }

    uwave.redis.set(`playlist:${id}`, playlist.id);
    return uwave.redis.get(`playlist:${id}`);
  });
};

export const createPlaylistItems = function createPlaylistItems(id, playlistID, items, uwave) {
  const PlaylistItem = uwave.mongo.model('PlaylistItem');
  const Playlist = uwave.mongo.model('Playlist');
  const Media = uwave.mongo.model('Media');

  const _items = [];

  const _addMedia = function(sourceType, sourceID) {
    let _playlistItem = null;

    return Media.findOne({ 'sourceType': sourceType, 'sourceID': sourceID })
    .then(media => {
      if (!media) {
        return addMedia(sourceType, sourceID, uwave.keys, Media);
      } else {
        return media;
      }
    })
    .then(media => {
      _playlistItem = new PlaylistItem({
        'media': media,
        'artist': media.artist,
        'title': media.title,
        'end': media.duration
      });

      return _playlistItem.save();
    })
    .then(playlistItem => {
      if (!playlistItem) throw new Error('couldn\'t save media');

      return playlistItem;
    }, e => {
      if (_playlistItem) _playlistItem.remove();
      throw e;
    });
  };

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (playlist.author.toString() !== id) throw new GenericError(403, 'you can\'t edit the playlist of another user');

    for (let i = 0, l = items.length; i < l; i++) {
      if (typeof items[i] !== 'object') continue;
      if (
        typeof items[i].sourceType !== 'string' ||
        (typeof items[i].sourceID !== 'string' && typeof items[i].sourceID !== 'number')
      ) continue;

      _items.push(_addMedia(items[i].sourceType, items[i].sourceID));
    }

    return Promise.all(_items)
    .then(playlistItems => {
      playlist.media = playlist.media.concat(playlistItems);

      return playlist.save()
      .then(() => {
        return {
          'added': playlistItems,
          'playlistSize': playlist.media.length
        };
      });
    });
  });
};

export const getPlaylistItem = function getPlaylistItem(id, playlistID, mediaID, mongo) {
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID)).populate('media')
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString() && playlist.shared) {
      throw new GenericError(403, 'this playlist is private');
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].id === mediaID) {
        return playlist.media[i];
      }
    }
    throw new GenericError(404, 'media not found');
  });
};

export const updatePlaylistItem = function updatePlaylistItem(id, playlistID, mediaID, metadata, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${playlistID} not found`);
    if (id !== playlist.author.toString() && playlist.shared) {
      throw new GenericError(403, 'playlist is private');
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].toString() === mediaID) {
        return PlaylistItem.findOneAndUpdate(playlist.media[i], metadata, { 'new': true });
      }
    }

    throw new GenericError(404, 'media not found');
  });
};

export const deletePlaylistItem = function deletePlaylistItem(id, playlistID, mediaID, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  return Playlist.findOne(ObjectId(playlistID))
  .then(playlist => {
    if (!playlist) throw new GenericError(404, `playlist with ID ${id} not found`);
    if (id !== playlist.author.toString()) {
      throw new GenericError(403, 'playlist is private');
    }

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlist.media[i].toString() === mediaID) {
        PlaylistItem.findOneAndRemove(playlist.media[i]);

        playlist.media.splice(i, 1);
        return playlist.save();
      }
    }

    throw new GenericError(404, 'media not found');
  });
};

export const copyPlaylistItem = function copyPlaylistItem(id, fromPlaylistID, mediaID, toPlaylistID, mongo) {
  const PlaylistItem = mongo.model('PlaylistItem');
  const Playlist = mongo.model('Playlist');

  let _playlistItem = null;

  return Playlist.findOne(ObjectId(fromPlaylistID)).populate('media')
  .then(playlist => {
    if (!playlist) throw new GenericError(404, 'originating playlist not found');
    if (playlist.author.toString() !== id && !playlist.shared) throw new GenericError(403, 'originating playlist is private');

    for (let i = playlist.media.length - 1; i >= 0; i--) {
      if (playlists.media[i].id === mediaID) {
        return PlaylistItem.findOne(ObjectId(mediaID), '-id');
      }
    }
  })
  .then(playlistItem => {
    return new PlaylistItem(playlistItem);
  })
  .then(playlistItem => {
    _playlistItem = playlistItem;
    return Playlist.findOne(ObjectId(toPlaylistID));
  })
  .then(playlist => {
    if (!playlist) throw new GenericError(404, 'playlist not found');
    if (!playlist.author.toString() !== id) throw new GenericError(403, 'you can\'t copy media to another user\'s playlist');
    playlist.media.push(_playlistItem.id);
    return playlist.save();
  });
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { withApollo } from 'react-apollo';
import gql from 'graphql-tag';
import withCollection from './collection';
import isEqual from 'lodash/isEqual';
import lowerFirst from 'lodash/lowerFirst';
import { withRouter } from '../router';

export default ({ name, state } = {}) => (WrappedComponent) => {
  @withCollection(name || '')
  @withApollo
  @withRouter
  class WithItemsComponent extends Component {
    static propTypes = {
      collection: PropTypes.object, // todo: shape statt object
      client: PropTypes.object, // todo: shape statt object
      onClick: PropTypes.func,
    };

    constructor(props) {
      super(props);
      this.items = null;
      this.state = {
        selectedRowKeys: [],
      };
    }

    componentWillMount() {
      this.update(this.props);
    }

    componentWillReceiveProps(props) {
      this.update(props, this.props);
    }

    componentWillUnmount() {
      if (this.subscription) this.subscription.unsubscribe();
      this.unmount = true;
    }

    setQuery = (query) => {
      this.newQuery = query;
      this.update({ ...this.props, force: true });
      this.forceUpdate();
    }

    update = (nextProps, lastProps) => {
      const oldType = lastProps && lastProps.collection && lastProps.collection.name;
      const newType = nextProps && nextProps.collection && nextProps.collection.name;
      if (!lastProps || oldType !== newType || !isEqual(nextProps.query, lastProps.query)) {
        if (this.subscription) this.subscription.unsubscribe();
        const { client, collection, fieldNames, location, force } = nextProps;
        if (!collection) return;
        const query = this.newQuery || this.props.query;
        this.items = null;
        const queryName = `${lowerFirst(collection.name)}List`;

        let watchQuery;
        if (query) {
          watchQuery = client.watchQuery({
            forceFetch: force,
            query: gql`
              query ${queryName}($query: ${collection.name}Query) {
                items: ${queryName}(query: $query) {
                  ${fieldNames}
                }
              }
            `,
            variables: {
              query,
            },
          });
        } else if (fieldNames.indexOf('state') !== -1) {
          watchQuery = client.watchQuery({
            forceFetch: force,
            query: gql`
              query ${queryName}($state: [DOCUMENT_STATE]) {
                items: ${queryName}(query: {state: {in: $state}}) {
                  ${fieldNames}
                }
              }
            `,
            variables: {
              state: state || (location.query && location.query.state ? location.query.state.split('-') : ['PUBLISHED']),
            },
          });
        } else {
          watchQuery = client.watchQuery({
            forceFetch: force,
            query: gql`
              query ${queryName} {
                items: ${queryName} {
                  ${fieldNames}
                }
              }
            `,
          });
        }
        this.isLoading = true;

        this.subscription = watchQuery.subscribe({
          next: ({ data }) => {
            if (this.unmount) return;
            this.items = data.items;
            this.isLoading = false;
            this.setState({ });
          },
          error: (error) => {
            console.log('there was an error sending the query', error);
          },
        });
      }
    }

    render() {
      return (
        <WrappedComponent {...this.props} items={this.items} refetch={this.setQuery} gqlQuery={this.newQuery || this.props.query} isLoading={this.isLoading} />
      );
    }
  }
  return WithItemsComponent;
};
<commit_msg>Final fix<commit_after>import React, { Component, PropTypes } from 'react';
import { withApollo } from 'react-apollo';
import gql from 'graphql-tag';
import withCollection from './collection';
import isEqual from 'lodash/isEqual';
import lowerFirst from 'lodash/lowerFirst';
import { withRouter } from '../router';

export default ({ name, state } = {}) => (WrappedComponent) => {
  @withCollection(name || '')
  @withApollo
  @withRouter
  class WithItemsComponent extends Component {
    static propTypes = {
      collection: PropTypes.object, // todo: shape statt object
      client: PropTypes.object, // todo: shape statt object
      onClick: PropTypes.func,
    };

    constructor(props) {
      super(props);
      this.items = null;
      this.state = {
        selectedRowKeys: [],
      };
    }

    componentWillMount() {
      this.update(this.props);
    }

    componentWillReceiveProps(props) {
      this.update(props, this.props);
    }

    componentWillUnmount() {
      if (this.subscription) this.subscription.unsubscribe();
      this.unmount = true;
    }

    setQuery = (gqlQuery) => {
      this.newQuery = gqlQuery;
      this.update({ ...this.props, force: true });
      this.forceUpdate();
    }

    update = (nextProps, lastProps) => {
      const oldType = lastProps && lastProps.collection && lastProps.collection.name;
      const newType = nextProps && nextProps.collection && nextProps.collection.name;
      if (!lastProps || oldType !== newType || !isEqual(nextProps.query, lastProps.query)) {
        if (this.subscription) this.subscription.unsubscribe();
        const { client, collection, fieldNames, location, force } = nextProps;
        if (!collection) return;
        const gqlQuery = this.newQuery || this.props.gqlQuery;
        this.items = null;
        const queryName = `${lowerFirst(collection.name)}List`;

        let watchQuery;
        if (gqlQuery) {
          watchQuery = client.watchQuery({
            forceFetch: force,
            query: gql`
              query ${queryName}($query: ${collection.name}Query) {
                items: ${queryName}(query: $query) {
                  ${fieldNames}
                }
              }
            `,
            variables: {
              gqlQuery,
            },
          });
        } else if (fieldNames.indexOf('state') !== -1) {
          watchQuery = client.watchQuery({
            forceFetch: force,
            query: gql`
              query ${queryName}($state: [DOCUMENT_STATE]) {
                items: ${queryName}(query: {state: {in: $state}}) {
                  ${fieldNames}
                }
              }
            `,
            variables: {
              state: state || (location.query && location.query.state ? location.query.state.split('-') : ['PUBLISHED']),
            },
          });
        } else {
          watchQuery = client.watchQuery({
            forceFetch: force,
            query: gql`
              query ${queryName} {
                items: ${queryName} {
                  ${fieldNames}
                }
              }
            `,
          });
        }
        this.isLoading = true;

        this.subscription = watchQuery.subscribe({
          next: ({ data }) => {
            if (this.unmount) return;
            this.items = data.items;
            this.isLoading = false;
            this.setState({ });
          },
          error: (error) => {
            console.log('there was an error sending the query', error);
          },
        });
      }
    }

    render() {
      return (
        <WrappedComponent {...this.props} items={this.items} refetch={this.setQuery} gqlQuery={this.newQuery || this.props.gqlQuery} isLoading={this.isLoading} />
      );
    }
  }
  return WithItemsComponent;
};
<|endoftext|>


<commit_before>// @flow
import type { ServiceAccessHash, Identity } from './'

export type Individual = {
  id: String,
  fullname: string,
  email: Array<string>,
  serviceIdentities: { [string]: Identity }, // { "github": "shamwow16" }
  accessRules: ServiceAccessHash,
  groups: Array<string>
}
<commit_msg>update individual type<commit_after>// @flow
import type { ServiceAccessHash, Identity } from './'

export type Individual = {
  id: String,
  fullname: string,
  primaryEmail: ?string,
  serviceIdentities: { [string]: Identity },
  accessRules: ServiceAccessHash,
  groups: Array<string>
}
<|endoftext|>


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

    angular.module('app').controller('RoomController', ['Restangular', '$stateParams', '$timeout', '$interval', '$q', 'localStorageService', '$scope', 'matchmedia', 'UpdateHub', function(Restangular, $stateParams, $timeout, $interval, $q, localStorageService, $scope, matchmedia, UpdateHub) {
        var self = this;

        self.Free = 0;
        self.Busy = 1;
        self.BusyNotConfirmed = 2;

        self.Requested = 1;
        self.Denied = 2;
        self.Granted = 3;

        self.moment = window.moment;
        self.displayName = 'Loading...';
        self.roomAddress = $stateParams.roomAddress;
        var room = Restangular.one('room', self.roomAddress);
        var securityKey = localStorageService.get('room_' + self.roomAddress) || '';
        self.isDefaultRoom = localStorageService.get('defaultRoom') == self.roomAddress;
        self.hasSecurityRights = false;

        var timeDelta = moment().diff(moment());
        self.currentTime = function currentTime() {
            return moment().add(-timeDelta, 'milliseconds').second(0).millisecond(0);
        };

        self.freeMinutes = function() {
            if(!self.current) {
                return null;
            }
            return self.moment(self.current.Start).diff(self.currentTime(), 'minutes');
        };

        function updateTimeline() {
            var now = self.currentTime();
            var day = now.clone().startOf('day');
            var start = day.clone().add(7, 'hours');
            var end = day.clone().add(18, 'hours');
            var totalMinutes = end.diff(start, 'minute', true);

            var marker = start;
            var markers = [];
            while(!marker.isAfter(end)) {
                markers.push({ position: marker.diff(start, 'minute', true) / totalMinutes, time: marker.isBefore(end) ? marker : null });
                marker = marker.clone().add(1, 'hour');
            }

            function buildTimelineRanges() {
                if(!self.appointments) {
                    return [{
                        size: 1,
                        isLoading: true
                    }];
                }
                var ranges = [];
                var current = start;
                _.each(self.appointments, function(item) {
                    var itemStart = moment(item.Start);
                    var itemEnd = moment(item.End);
                    if(!itemStart.isBefore(end) || !itemEnd.isAfter(current)){
                        return;
                    }
                    var freeTime = itemStart.diff(current, 'minute', true);
                    if(freeTime > 0) {
                        ranges.push({
                            size: freeTime/totalMinutes
                        });
                    }
                    var time = moment.min(itemEnd, end).diff(itemStart, 'minute', true);
                    if(time > 0) {
                        ranges.push({
                            size: time/totalMinutes,
                            appointment: item
                        })
                    }

                    current = itemEnd;
                });

                if(current.isBefore(end)) {
                    ranges.push({
                        size: end.diff(current, 'minute', true)/totalMinutes
                    });
                }

                return ranges;
            }

            self.timeline = {
                now: { position: Math.min(1.01, Math.max(-0.01, now.diff(start, 'minute', true) / totalMinutes)), time: now },
                markers: markers,
                ranges: buildTimelineRanges()
            };
        }

        var infoTimeout = null;
        function loadInfo() {
            if(infoTimeout) {
                $timeout.cancel(infoTimeout);
            }
            return $q.when(room.one('info').get({ securityKey: securityKey })).then(function(data) {
                timeDelta = moment().diff(moment(data.CurrentTime));
                self.displayName = data.DisplayName;
                self.hasSecurityRights = data.SecurityStatus == 3; // granted
                updateTimeline();
                scheduleCancel();
            }, function() {
                infoTimeout = $timeout(loadInfo, 60 * 1000); // if load failed, try again in 60 seconds
            });
        }

        var statusTimeout = null;
        var cancelTimeout = null;
        var warnings = {};

        function scheduleCancel() {
            if(!self.hasSecurityRights) {
                return;
            }
            if(cancelTimeout) {
                $timeout.cancel(cancelTimeout);
            }
            if(!self.current || self.current.IsStarted) {
                return;
            }
            if(self.IsNotManaged) {
                return; // unmanaged meeting
            }

            var now = self.currentTime();
            var warnTime = warnings[self.current.UniqueId];
            if(!warnTime) {
                // we haven't warned yet - figure out when we should
                warnTime = moment(self.current.Start).add(4, 'minute');
                if(!warnTime.isAfter(now)) {
                    // whoops, we should have done that already.... let's do it now.
                    room.one('meeting').post('warnAbandon', {}, { securityKey: securityKey, uniqueId: self.current.UniqueId }).then(function() {
                        // ok, we've told people, just remember what time it is so we give them a minute to start the meeting
                        warnings[self.current.UniqueId] = self.currentTime();
                        if(cancelTimeout) {
                            $timeout.cancel(cancelTimeout);
                        }
                        cancelTimeout = $timeout(scheduleCancel, 61 * 1000);
                    }, function() {
                        // well, the warning failed... maybe the item was deleted?  In any case, reloading the status will re-run us
                        $timeout(loadStatus, 1000);
                    });
                } else {
                    // ok, not time to warn yet - re-run once it's time
                    cancelTimeout = $timeout(scheduleCancel, warnTime.diff(now, 'millisecond', true) + 1000);
                }
                return;
            }

            // ok, we've warned.  Have they had a minute to get back to us yet?
            var canCancelAt = warnTime.clone().add(1, 'minute');
            if(!canCancelAt.isAfter(now)) {
                // they've taken too long - cancel it now
                room.one('meeting').post('abandon', {}, { securityKey: securityKey, uniqueId: self.current.UniqueId }).then(function() {
                    // ok, meeting is cancelled.  Just refresh
                    loadStatus();
                }, function() {
                    // well, the cancel failed... maybe the item was deleted?  In any case, reloading the status will re-run us
                    $timeout(loadStatus, 1000);
                });
            } else {
                // we need to give them some more time
                cancelTimeout = $timeout(scheduleCancel, canCancelAt.diff(now, 'millisecond', true) + 1000);
            }
        }

        function loadStatus() {
            if(statusTimeout) {
                $timeout.cancel(statusTimeout);
            }

            return room.one('status').get().then(function(data) {
                self.status = data.Status;
                self.appointments = _.sortBy(data.NearTermMeetings, 'Start');
                self.current = data.CurrentMeeting;
                self.next = data.NextMeeting;
                updateTimeline();
                scheduleCancel();

                var waitTime = data.NextChangeSeconds ? Math.min(5 * 60, data.NextChangeSeconds + 1) : (5 * 60);
                statusTimeout = $timeout(loadStatus, waitTime * 1000);
            }, function() {
                statusTimeout = $timeout(loadStatus, 60 * 1000);
            });
        }

        self.start = function(item) {
            var p = room.one('meeting').post('start', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.cancel = function(item) {
            var p = room.one('meeting').post('abandon', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.end = function(item) {
            var p = room.one('meeting').post('end', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.endAndStartNext = function(item, next) {
            var p = room.one('meeting').post('end', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return room.one('meeting').post('start', {}, { securityKey: securityKey, uniqueId: next.UniqueId }).then(function() {
                    return loadStatus();
                });
            });
            showIndicator(p);
        };
        self.requestControl = function() {
            if(!securityKey) {
                securityKey = (''+Math.random()).replace('.','');
                localStorageService.set('room_' + self.roomAddress, securityKey);
            }
            var p = room.post('requestAccess', {}, { securityKey: securityKey }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.setDefaultRoom = function() {
            localStorageService.set('defaultRoom', self.roomAddress);
            self.isDefaultRoom = localStorageService.get('defaultRoom') == self.roomAddress;
        };
        self.refresh = function() {
            var p = $q.all([loadInfo(), loadStatus()]);
            showIndicator(p);
        };

        self.newMeetingTime = 15;
        self.createNewMeeting = function newMeeting() {
            var p = room.one('meeting').post('startNew', {}, { securityKey: securityKey, title: 'New Meeting', minutes: self.newMeetingTime }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };

        function showIndicator(loadPromise) {
            self.isLoading = true;
            loadPromise.then(function() {
                self.isLoading = false;
            });
        }

        self.refresh();

        self.isTiny = false;
        var isTinyDispose = matchmedia.on('(max-width: 320px) and (max-height: 240px)', function(mql) {
            self.isTiny = mql.matches;
        });

        var infoInterval = $interval(loadInfo, 60 * 60 * 1000);
        var scopeCycleInterval = $interval(function() {}, 10 * 1000);

        $scope.$on('roomRefresh', function(event, room) {
            if(self.roomAddress == room) {
                // don't trigger the spinners
                loadInfo();
                loadStatus();
            }
        });

        $scope.$on('$destroy', function() {
            if(infoInterval) {
                $interval.cancel(infoInterval);
            }
            if(infoTimeout) {
                $timeout.cancel(infoTimeout);
            }
            if(statusTimeout) {
                $timeout.cancel(statusTimeout);
            }
            if(cancelTimeout) {
                $timeout.cancel(cancelTimeout);
            }
            if(scopeCycleInterval) {
                $interval.cancel(scopeCycleInterval);
            }
            isTinyDispose();
        });

    }]);
})();<commit_msg>Fix ignoring of non-managed meetings<commit_after>(function() {
    'use strict;'

    angular.module('app').controller('RoomController', ['Restangular', '$stateParams', '$timeout', '$interval', '$q', 'localStorageService', '$scope', 'matchmedia', 'UpdateHub', function(Restangular, $stateParams, $timeout, $interval, $q, localStorageService, $scope, matchmedia, UpdateHub) {
        var self = this;

        self.Free = 0;
        self.Busy = 1;
        self.BusyNotConfirmed = 2;

        self.Requested = 1;
        self.Denied = 2;
        self.Granted = 3;

        self.moment = window.moment;
        self.displayName = 'Loading...';
        self.roomAddress = $stateParams.roomAddress;
        var room = Restangular.one('room', self.roomAddress);
        var securityKey = localStorageService.get('room_' + self.roomAddress) || '';
        self.isDefaultRoom = localStorageService.get('defaultRoom') == self.roomAddress;
        self.hasSecurityRights = false;

        var timeDelta = moment().diff(moment());
        self.currentTime = function currentTime() {
            return moment().add(-timeDelta, 'milliseconds').second(0).millisecond(0);
        };

        self.freeMinutes = function() {
            if(!self.current) {
                return null;
            }
            return self.moment(self.current.Start).diff(self.currentTime(), 'minutes');
        };

        function updateTimeline() {
            var now = self.currentTime();
            var day = now.clone().startOf('day');
            var start = day.clone().add(7, 'hours');
            var end = day.clone().add(18, 'hours');
            var totalMinutes = end.diff(start, 'minute', true);

            var marker = start;
            var markers = [];
            while(!marker.isAfter(end)) {
                markers.push({ position: marker.diff(start, 'minute', true) / totalMinutes, time: marker.isBefore(end) ? marker : null });
                marker = marker.clone().add(1, 'hour');
            }

            function buildTimelineRanges() {
                if(!self.appointments) {
                    return [{
                        size: 1,
                        isLoading: true
                    }];
                }
                var ranges = [];
                var current = start;
                _.each(self.appointments, function(item) {
                    var itemStart = moment(item.Start);
                    var itemEnd = moment(item.End);
                    if(!itemStart.isBefore(end) || !itemEnd.isAfter(current)){
                        return;
                    }
                    var freeTime = itemStart.diff(current, 'minute', true);
                    if(freeTime > 0) {
                        ranges.push({
                            size: freeTime/totalMinutes
                        });
                    }
                    var time = moment.min(itemEnd, end).diff(itemStart, 'minute', true);
                    if(time > 0) {
                        ranges.push({
                            size: time/totalMinutes,
                            appointment: item
                        })
                    }

                    current = itemEnd;
                });

                if(current.isBefore(end)) {
                    ranges.push({
                        size: end.diff(current, 'minute', true)/totalMinutes
                    });
                }

                return ranges;
            }

            self.timeline = {
                now: { position: Math.min(1.01, Math.max(-0.01, now.diff(start, 'minute', true) / totalMinutes)), time: now },
                markers: markers,
                ranges: buildTimelineRanges()
            };
        }

        var infoTimeout = null;
        function loadInfo() {
            if(infoTimeout) {
                $timeout.cancel(infoTimeout);
            }
            return $q.when(room.one('info').get({ securityKey: securityKey })).then(function(data) {
                timeDelta = moment().diff(moment(data.CurrentTime));
                self.displayName = data.DisplayName;
                self.hasSecurityRights = data.SecurityStatus == 3; // granted
                updateTimeline();
                scheduleCancel();
            }, function() {
                infoTimeout = $timeout(loadInfo, 60 * 1000); // if load failed, try again in 60 seconds
            });
        }

        var statusTimeout = null;
        var cancelTimeout = null;
        var warnings = {};

        function scheduleCancel() {
            if(!self.hasSecurityRights) {
                return;
            }
            if(cancelTimeout) {
                $timeout.cancel(cancelTimeout);
            }
            if(!self.current || self.current.IsStarted) {
                return;
            }
            if(self.current.IsNotManaged) {
                return; // unmanaged meeting
            }

            var now = self.currentTime();
            var warnTime = warnings[self.current.UniqueId];
            if(!warnTime) {
                // we haven't warned yet - figure out when we should
                warnTime = moment(self.current.Start).add(4, 'minute');
                if(!warnTime.isAfter(now)) {
                    // whoops, we should have done that already.... let's do it now.
                    room.one('meeting').post('warnAbandon', {}, { securityKey: securityKey, uniqueId: self.current.UniqueId }).then(function() {
                        // ok, we've told people, just remember what time it is so we give them a minute to start the meeting
                        warnings[self.current.UniqueId] = self.currentTime();
                        if(cancelTimeout) {
                            $timeout.cancel(cancelTimeout);
                        }
                        cancelTimeout = $timeout(scheduleCancel, 61 * 1000);
                    }, function() {
                        // well, the warning failed... maybe the item was deleted?  In any case, reloading the status will re-run us
                        $timeout(loadStatus, 1000);
                    });
                } else {
                    // ok, not time to warn yet - re-run once it's time
                    cancelTimeout = $timeout(scheduleCancel, warnTime.diff(now, 'millisecond', true) + 1000);
                }
                return;
            }

            // ok, we've warned.  Have they had a minute to get back to us yet?
            var canCancelAt = warnTime.clone().add(1, 'minute');
            if(!canCancelAt.isAfter(now)) {
                // they've taken too long - cancel it now
                room.one('meeting').post('abandon', {}, { securityKey: securityKey, uniqueId: self.current.UniqueId }).then(function() {
                    // ok, meeting is cancelled.  Just refresh
                    loadStatus();
                }, function() {
                    // well, the cancel failed... maybe the item was deleted?  In any case, reloading the status will re-run us
                    $timeout(loadStatus, 1000);
                });
            } else {
                // we need to give them some more time
                cancelTimeout = $timeout(scheduleCancel, canCancelAt.diff(now, 'millisecond', true) + 1000);
            }
        }

        function loadStatus() {
            if(statusTimeout) {
                $timeout.cancel(statusTimeout);
            }

            return room.one('status').get().then(function(data) {
                self.status = data.Status;
                self.appointments = _.sortBy(data.NearTermMeetings, 'Start');
                self.current = data.CurrentMeeting;
                self.next = data.NextMeeting;
                updateTimeline();
                scheduleCancel();

                var waitTime = data.NextChangeSeconds ? Math.min(5 * 60, data.NextChangeSeconds + 1) : (5 * 60);
                statusTimeout = $timeout(loadStatus, waitTime * 1000);
            }, function() {
                statusTimeout = $timeout(loadStatus, 60 * 1000);
            });
        }

        self.start = function(item) {
            var p = room.one('meeting').post('start', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.cancel = function(item) {
            var p = room.one('meeting').post('abandon', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.end = function(item) {
            var p = room.one('meeting').post('end', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.endAndStartNext = function(item, next) {
            var p = room.one('meeting').post('end', {}, { securityKey: securityKey, uniqueId: item.UniqueId }).then(function() {
                return room.one('meeting').post('start', {}, { securityKey: securityKey, uniqueId: next.UniqueId }).then(function() {
                    return loadStatus();
                });
            });
            showIndicator(p);
        };
        self.requestControl = function() {
            if(!securityKey) {
                securityKey = (''+Math.random()).replace('.','');
                localStorageService.set('room_' + self.roomAddress, securityKey);
            }
            var p = room.post('requestAccess', {}, { securityKey: securityKey }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };
        self.setDefaultRoom = function() {
            localStorageService.set('defaultRoom', self.roomAddress);
            self.isDefaultRoom = localStorageService.get('defaultRoom') == self.roomAddress;
        };
        self.refresh = function() {
            var p = $q.all([loadInfo(), loadStatus()]);
            showIndicator(p);
        };

        self.newMeetingTime = 15;
        self.createNewMeeting = function newMeeting() {
            var p = room.one('meeting').post('startNew', {}, { securityKey: securityKey, title: 'New Meeting', minutes: self.newMeetingTime }).then(function() {
                return loadStatus();
            });
            showIndicator(p);
        };

        function showIndicator(loadPromise) {
            self.isLoading = true;
            loadPromise.then(function() {
                self.isLoading = false;
            });
        }

        self.refresh();

        self.isTiny = false;
        var isTinyDispose = matchmedia.on('(max-width: 320px) and (max-height: 240px)', function(mql) {
            self.isTiny = mql.matches;
        });

        var infoInterval = $interval(loadInfo, 60 * 60 * 1000);
        var scopeCycleInterval = $interval(function() {}, 10 * 1000);

        $scope.$on('roomRefresh', function(event, room) {
            if(self.roomAddress == room) {
                // don't trigger the spinners
                loadInfo();
                loadStatus();
            }
        });

        $scope.$on('$destroy', function() {
            if(infoInterval) {
                $interval.cancel(infoInterval);
            }
            if(infoTimeout) {
                $timeout.cancel(infoTimeout);
            }
            if(statusTimeout) {
                $timeout.cancel(statusTimeout);
            }
            if(cancelTimeout) {
                $timeout.cancel(cancelTimeout);
            }
            if(scopeCycleInterval) {
                $interval.cancel(scopeCycleInterval);
            }
            isTinyDispose();
        });

    }]);
})();<|endoftext|>


<commit_before>const { evaluators } = require('arsenal').policies;
const constants = require('../../../../constants');

const actionMap = {
    's3:AbortMultipartUpload': 'multipartDelete',
    's3:DeleteBucket': 'bucketDelete',
    's3:DeleteBucketPolicy': 'bucketDeletePolicy',
    's3:DeleteBucketWebsite': 'bucketDeleteWebsite',
    's3:DeleteObject': 'objectDelete',
    's3:DeleteObjectTagging': 'objectDeleteTagging',
    's3:GetBucketAcl': 'bucketGetACL',
    's3:GetBucketCORS': 'bucketGetCors',
    's3:GetBucketLocation': 'bucketGetLocation',
    's3:GetBucketPolicy': 'bucketGetPolicy',
    's3:GetBucketVersioning': 'bucketGetVersioning',
    's3:GetBucketWebsite': 'bucketGetWebsite',
    's3:GetLifecycleConfiguration': 'bucketGetLifecycle',
    's3:GetObject': 'objectGet',
    's3:GetObjectAcl': 'objectGetACL',
    's3:GetObjectTagging': 'objectGetTagging',
    's3:GetReplicationConfiguration': 'bucketGetReplication',
    's3:ListBucket': 'bucketHead',
    's3:ListBucketMultipartUploads': 'listMultipartUploads',
    's3:ListMultipartUploadParts': 'listParts',
    's3:PutBucketAcl': 'bucketPutACL',
    's3:PutBucketCORS': 'bucketPutCors',
    's3:PutBucketPolicy': 'bucketPutPolicy',
    's3:PutBucketVersioning': 'bucketPutVersioning',
    's3:PutBucketWebsite': 'bucketPutWebsite',
    's3:PutLifecycleConfiguration': 'bucketPutLifecycle',
    's3:PutObject': 'objectPut',
    's3:PutObjectAcl': 'objectPutACL',
    's3:PutObjectTagging': 'objectPutTagging',
    's3:PutReplicationConfiguration': 'bucketPutReplication',
};

// whitelist buckets to allow public read on objects
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ?
    process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];

function checkBucketAcls(bucket, requestType, canonicalID) {
    if (bucket.getOwner() === canonicalID) {
        return true;
    }
    if (constants.bucketOwnerActions.includes(requestType)) {
        // only bucket owner can modify or retrieve this property of a bucket
        return false;
    }

    const bucketAcl = bucket.getAcl();
    if (requestType === 'bucketGet' || requestType === 'bucketHead') {
        if (bucketAcl.Canned === 'public-read'
            || bucketAcl.Canned === 'public-read-write'
            || (bucketAcl.Canned === 'authenticated-read'
                && canonicalID !== constants.publicId)) {
            return true;
        } else if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.READ.indexOf(canonicalID) > -1) {
            return true;
        }
    }
    if (requestType === 'bucketGetACL') {
        if ((bucketAcl.Canned === 'log-delivery-write'
                && canonicalID === constants.logId)
            || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.READ_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    if (requestType === 'bucketPutACL') {
        if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    if (requestType === 'bucketDelete' && bucket.getOwner() === canonicalID) {
        return true;
    }

    if (requestType === 'objectDelete' || requestType === 'objectPut') {
        if (bucketAcl.Canned === 'public-read-write'
            || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.WRITE.indexOf(canonicalID) > -1) {
            return true;
        }
    }
    // Note that an account can have the ability to do objectPutACL,
    // objectGetACL, objectHead or objectGet even if the account has no rights
    // to the bucket holding the object.  So, if the request type is
    // objectPutACL, objectGetACL, objectHead or objectGet, the bucket
    // authorization check should just return true so can move on to check
    // rights at the object level.
    return (requestType === 'objectPutACL' || requestType === 'objectGetACL' ||
    requestType === 'objectGet' || requestType === 'objectHead');
}

function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
    if (objectMD['owner-id'] === canonicalID) {
        return true;
    }
    if (!objectMD.acl) {
        return false;
    }
    const bucketOwner = bucket.getOwner();

    if (requestType === 'objectGet' || requestType === 'objectHead') {
        if (objectMD.acl.Canned === 'public-read'
            || objectMD.acl.Canned === 'public-read-write'
            || (objectMD.acl.Canned === 'authenticated-read'
                && canonicalID !== constants.publicId)) {
            return true;
        } else if (objectMD.acl.Canned === 'bucket-owner-read'
                && bucketOwner === canonicalID) {
            return true;
        } else if ((objectMD.acl.Canned === 'bucket-owner-full-control'
                && bucketOwner === canonicalID)
            || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
            || objectMD.acl.READ.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    // User is already authorized on the bucket for FULL_CONTROL or WRITE or
    // bucket has canned ACL public-read-write
    if (requestType === 'objectPut' || requestType === 'objectDelete') {
        return true;
    }

    if (requestType === 'objectPutACL') {
        if ((objectMD.acl.Canned === 'bucket-owner-full-control'
                && bucketOwner === canonicalID)
            || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
            || objectMD.acl.WRITE_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    if (requestType === 'objectGetACL') {
        if ((objectMD.acl.Canned === 'bucket-owner-full-control'
                && bucketOwner === canonicalID)
            || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
            || objectMD.acl.READ_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    // allow public reads on buckets that are whitelisted for anonymous reads
    // TODO: remove this after bucket policies are implemented
    const bucketAcl = bucket.getAcl();
    const allowPublicReads = publicReadBuckets.includes(bucket.getName()) &&
        bucketAcl.Canned === 'public-read' &&
        (requestType === 'objectGet' || requestType === 'objectHead');
    if (allowPublicReads) {
        return true;
    }
    return false;
}

function _checkActions(requestType, actions, log) {
    // if requestType isn't in list of controlled actions
    if (!Object.values(actionMap).includes(requestType)) {
        return true;
    }
    const mappedAction = Object.keys(actionMap)
        [Object.values(actionMap).indexOf(requestType)];
    return evaluators.isActionApplicable(mappedAction, actions, log);
}

function _getAccountId(arn) {
    // account or user arn is of format 'arn:aws:iam::<12-digit-acct-id>:etc...
    return arn.substr(13, 12);
}

function _isAccountId(principal) {
    return (principal.length === 12 && /^\d+$/.test(principal));
}

function _checkPrincipal(requester, principal) {
    if (principal === '*') {
        return true;
    }
    if (principal === requester) {
        return true;
    }
    if (_isAccountId(principal)) {
        return _getAccountId(requester) === principal;
    }
    if (principal.endsWith('root')) {
        return _getAccountId(requester) === _getAccountId(principal);
    }
    return false;
}

function _checkPrincipals(canonicalID, arn, principal) {
    if (principal === '*') {
        return true;
    }
    if (principal.CanonicalUser) {
        if (Array.isArray(principal.CanonicalUser)) {
            return principal.CanonicalUser.some(p => _checkPrincipal(canonicalID, p));
        }
        return _checkPrincipal(canonicalID, principal.CanonicalUser);
    }
    if (principal.AWS) {
        if (Array.isArray(principal.AWS)) {
            return principal.AWS.some(p => _checkPrincipal(arn, p));
        }
        return _checkPrincipal(arn, principal.AWS);
    }
    return false;
}

function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, log) {
    let permission = 'defaultDeny';
    // if requester is user within bucket owner account, actions should be
    // allowed unless explicitly denied (assumes allowed by IAM policy)
    if (bucketOwner === canonicalID) {
        permission = 'allow';
    }
    let copiedStatement = JSON.parse(JSON.stringify(policy.Statement));
    while (copiedStatement.length > 0) {
        const s = copiedStatement[0];
        const principalMatch = _checkPrincipals(canonicalID, arn, s.Principal);
        const actionMatch = _checkActions(requestType, s.Action, log);

        if (principalMatch && actionMatch && s.Effect === 'Deny') {
            // explicit deny trumps any allows, so return immediately
            return 'explicitDeny';
        }
        if (principalMatch && actionMatch && s.Effect === 'Allow') {
            permission = 'allow';
        }
        copiedStatement = copiedStatement.splice(1);
    }
    return permission;
}

function isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log) {
    // Check to see if user is authorized to perform a
    // particular action on bucket based on ACLs.
    // TODO: Add IAM checks
    let requesterIsNotUser = true;
    let arn = null;
    if (authInfo) {
        requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
        arn = authInfo.getArn();
    }
    // if the bucket owner is an account, users should not have default access
    if ((bucket.getOwner() === canonicalID) && requesterIsNotUser) {
        return true;
    }
    const aclPermission = checkBucketAcls(bucket, requestType, canonicalID);
    const bucketPolicy = bucket.getBucketPolicy();
    if (!bucketPolicy) {
        if (constants.bucketOwnerActions.includes(requestType)) {
            return false;
        }
        return aclPermission;
    }
    const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
        canonicalID, arn, bucket.getOwner(), log);
    if (bucketPolicyPermission === 'explicitDeny') {
        return false;
    }
    return (aclPermission || (bucketPolicyPermission === 'allow'));
}

function isObjAuthorized(bucket, objectMD, requestType, canonicalID, authInfo, log) {
    const bucketOwner = bucket.getOwner();
    if (!objectMD) {
        return false;
    }
    let requesterIsNotUser = true;
    let arn = null;
    if (authInfo) {
        requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
        arn = authInfo.getArn();
    }
    if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) {
        return true;
    }
    // account is authorized if:
    // - requesttype is included in bucketOwnerActions and
    // - account is the bucket owner
    // - requester is account, not user
    if (constants.bucketOwnerActions.includes(requestType)
    && (bucketOwner === canonicalID)
    && requesterIsNotUser) {
        return true;
    }
    const aclPermission = checkObjectAcls(bucket, objectMD, requestType,
        canonicalID);
    const bucketPolicy = bucket.getBucketPolicy();
    if (!bucketPolicy) {
        return aclPermission;
    }
    const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
        canonicalID, arn, bucket.getOwner(), log);
    if (bucketPolicyPermission === 'explicitDeny') {
        return false;
    }
    return (aclPermission || (bucketPolicyPermission === 'allow'));
}

function _checkResource(resource, bucketArn) {
    if (resource === bucketArn) {
        return true;
    }
    if (resource.includes('/')) {
        const rSubs = resource.split('/');
        return rSubs[0] === bucketArn;
    }
    return false;
}

// the resources specified in the bucket policy should contain the bucket name
function validatePolicyResource(bucketName, policy) {
    const bucketArn = `arn:aws:s3:::${bucketName}`;

    return policy.Statement.every(s => {
        if (Array.isArray(s.Resource)) {
            return s.Resource.every(r => _checkResource(r, bucketArn));
        }
        if (typeof s.Resource === 'string') {
            return _checkResource(s.Resource, bucketArn);
        }
        return false;
    });
}

module.exports = {
    isBucketAuthorized,
    isObjAuthorized,
    checkBucketAcls,
    checkObjectAcls,
    validatePolicyResource,
};
<commit_msg>bf: S3C 2714 remove 'bucketOwnerAction' from bucket perms and fix obj logic<commit_after>const { evaluators } = require('arsenal').policies;
const constants = require('../../../../constants');

const actionMap = {
    's3:AbortMultipartUpload': 'multipartDelete',
    's3:DeleteBucket': 'bucketDelete',
    's3:DeleteBucketPolicy': 'bucketDeletePolicy',
    's3:DeleteBucketWebsite': 'bucketDeleteWebsite',
    's3:DeleteObject': 'objectDelete',
    's3:DeleteObjectTagging': 'objectDeleteTagging',
    's3:GetBucketAcl': 'bucketGetACL',
    's3:GetBucketCORS': 'bucketGetCors',
    's3:GetBucketLocation': 'bucketGetLocation',
    's3:GetBucketPolicy': 'bucketGetPolicy',
    's3:GetBucketVersioning': 'bucketGetVersioning',
    's3:GetBucketWebsite': 'bucketGetWebsite',
    's3:GetLifecycleConfiguration': 'bucketGetLifecycle',
    's3:GetObject': 'objectGet',
    's3:GetObjectAcl': 'objectGetACL',
    's3:GetObjectTagging': 'objectGetTagging',
    's3:GetReplicationConfiguration': 'bucketGetReplication',
    's3:ListBucket': 'bucketHead',
    's3:ListBucketMultipartUploads': 'listMultipartUploads',
    's3:ListMultipartUploadParts': 'listParts',
    's3:PutBucketAcl': 'bucketPutACL',
    's3:PutBucketCORS': 'bucketPutCors',
    's3:PutBucketPolicy': 'bucketPutPolicy',
    's3:PutBucketVersioning': 'bucketPutVersioning',
    's3:PutBucketWebsite': 'bucketPutWebsite',
    's3:PutLifecycleConfiguration': 'bucketPutLifecycle',
    's3:PutObject': 'objectPut',
    's3:PutObjectAcl': 'objectPutACL',
    's3:PutObjectTagging': 'objectPutTagging',
    's3:PutReplicationConfiguration': 'bucketPutReplication',
};

// whitelist buckets to allow public read on objects
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ?
    process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];

function checkBucketAcls(bucket, requestType, canonicalID) {
    if (bucket.getOwner() === canonicalID) {
        return true;
    }

    // any requestType outside of those checked in this function is
    // outside the scope of ACL permissions and will be denied unless
    // allowed by a different permissions granter
    const bucketAcl = bucket.getAcl();
    if (requestType === 'bucketGet' || requestType === 'bucketHead') {
        if (bucketAcl.Canned === 'public-read'
            || bucketAcl.Canned === 'public-read-write'
            || (bucketAcl.Canned === 'authenticated-read'
                && canonicalID !== constants.publicId)) {
            return true;
        } else if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.READ.indexOf(canonicalID) > -1) {
            return true;
        }
    }
    if (requestType === 'bucketGetACL') {
        if ((bucketAcl.Canned === 'log-delivery-write'
                && canonicalID === constants.logId)
            || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.READ_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    if (requestType === 'bucketPutACL') {
        if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    if (requestType === 'bucketDelete' && bucket.getOwner() === canonicalID) {
        return true;
    }

    if (requestType === 'objectDelete' || requestType === 'objectPut') {
        if (bucketAcl.Canned === 'public-read-write'
            || bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
            || bucketAcl.WRITE.indexOf(canonicalID) > -1) {
            return true;
        }
    }
    // Note that an account can have the ability to do objectPutACL,
    // objectGetACL, objectHead or objectGet even if the account has no rights
    // to the bucket holding the object.  So, if the request type is
    // objectPutACL, objectGetACL, objectHead or objectGet, the bucket
    // authorization check should just return true so can move on to check
    // rights at the object level.
    return (requestType === 'objectPutACL' || requestType === 'objectGetACL' ||
    requestType === 'objectGet' || requestType === 'objectHead');
}

function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
    const bucketOwner = bucket.getOwner();
    // acls don't distinguish between users and accounts, so both should be allowed
    if (constants.bucketOwnerActions.includes(requestType)
    && (bucketOwner === canonicalID)) {
        return true;
    }
    if (objectMD['owner-id'] === canonicalID) {
        return true;
    }
    if (!objectMD.acl) {
        return false;
    }

    if (requestType === 'objectGet' || requestType === 'objectHead') {
        if (objectMD.acl.Canned === 'public-read'
            || objectMD.acl.Canned === 'public-read-write'
            || (objectMD.acl.Canned === 'authenticated-read'
                && canonicalID !== constants.publicId)) {
            return true;
        } else if (objectMD.acl.Canned === 'bucket-owner-read'
                && bucketOwner === canonicalID) {
            return true;
        } else if ((objectMD.acl.Canned === 'bucket-owner-full-control'
                && bucketOwner === canonicalID)
            || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
            || objectMD.acl.READ.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    // User is already authorized on the bucket for FULL_CONTROL or WRITE or
    // bucket has canned ACL public-read-write
    if (requestType === 'objectPut' || requestType === 'objectDelete') {
        return true;
    }

    if (requestType === 'objectPutACL') {
        if ((objectMD.acl.Canned === 'bucket-owner-full-control'
                && bucketOwner === canonicalID)
            || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
            || objectMD.acl.WRITE_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    if (requestType === 'objectGetACL') {
        if ((objectMD.acl.Canned === 'bucket-owner-full-control'
                && bucketOwner === canonicalID)
            || objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
            || objectMD.acl.READ_ACP.indexOf(canonicalID) > -1) {
            return true;
        }
    }

    // allow public reads on buckets that are whitelisted for anonymous reads
    // TODO: remove this after bucket policies are implemented
    const bucketAcl = bucket.getAcl();
    const allowPublicReads = publicReadBuckets.includes(bucket.getName()) &&
        bucketAcl.Canned === 'public-read' &&
        (requestType === 'objectGet' || requestType === 'objectHead');
    if (allowPublicReads) {
        return true;
    }
    return false;
}

function _checkActions(requestType, actions, log) {
    // if requestType isn't in list of controlled actions
    if (!Object.values(actionMap).includes(requestType)) {
        return true;
    }
    const mappedAction = Object.keys(actionMap)
        [Object.values(actionMap).indexOf(requestType)];
    return evaluators.isActionApplicable(mappedAction, actions, log);
}

function _getAccountId(arn) {
    // account or user arn is of format 'arn:aws:iam::<12-digit-acct-id>:etc...
    return arn.substr(13, 12);
}

function _isAccountId(principal) {
    return (principal.length === 12 && /^\d+$/.test(principal));
}

function _checkPrincipal(requester, principal) {
    if (principal === '*') {
        return true;
    }
    if (principal === requester) {
        return true;
    }
    if (_isAccountId(principal)) {
        return _getAccountId(requester) === principal;
    }
    if (principal.endsWith('root')) {
        return _getAccountId(requester) === _getAccountId(principal);
    }
    return false;
}

function _checkPrincipals(canonicalID, arn, principal) {
    if (principal === '*') {
        return true;
    }
    if (principal.CanonicalUser) {
        if (Array.isArray(principal.CanonicalUser)) {
            return principal.CanonicalUser.some(p => _checkPrincipal(canonicalID, p));
        }
        return _checkPrincipal(canonicalID, principal.CanonicalUser);
    }
    if (principal.AWS) {
        if (Array.isArray(principal.AWS)) {
            return principal.AWS.some(p => _checkPrincipal(arn, p));
        }
        return _checkPrincipal(arn, principal.AWS);
    }
    return false;
}

function checkBucketPolicy(policy, requestType, canonicalID, arn, bucketOwner, log) {
    let permission = 'defaultDeny';
    // if requester is user within bucket owner account, actions should be
    // allowed unless explicitly denied (assumes allowed by IAM policy)
    if (bucketOwner === canonicalID) {
        permission = 'allow';
    }
    let copiedStatement = JSON.parse(JSON.stringify(policy.Statement));
    while (copiedStatement.length > 0) {
        const s = copiedStatement[0];
        const principalMatch = _checkPrincipals(canonicalID, arn, s.Principal);
        const actionMatch = _checkActions(requestType, s.Action, log);

        if (principalMatch && actionMatch && s.Effect === 'Deny') {
            // explicit deny trumps any allows, so return immediately
            return 'explicitDeny';
        }
        if (principalMatch && actionMatch && s.Effect === 'Allow') {
            permission = 'allow';
        }
        copiedStatement = copiedStatement.splice(1);
    }
    return permission;
}

function isBucketAuthorized(bucket, requestType, canonicalID, authInfo, log) {
    // Check to see if user is authorized to perform a
    // particular action on bucket based on ACLs.
    // TODO: Add IAM checks
    let requesterIsNotUser = true;
    let arn = null;
    if (authInfo) {
        requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
        arn = authInfo.getArn();
    }
    // if the bucket owner is an account, users should not have default access
    if ((bucket.getOwner() === canonicalID) && requesterIsNotUser) {
        return true;
    }
    const aclPermission = checkBucketAcls(bucket, requestType, canonicalID);
    const bucketPolicy = bucket.getBucketPolicy();
    if (!bucketPolicy) {
        return aclPermission;
    }
    const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
        canonicalID, arn, bucket.getOwner(), log);
    if (bucketPolicyPermission === 'explicitDeny') {
        return false;
    }
    return (aclPermission || (bucketPolicyPermission === 'allow'));
}

function isObjAuthorized(bucket, objectMD, requestType, canonicalID, authInfo, log) {
    const bucketOwner = bucket.getOwner();
    if (!objectMD) {
        return false;
    }
    let requesterIsNotUser = true;
    let arn = null;
    if (authInfo) {
        requesterIsNotUser = !authInfo.isRequesterAnIAMUser();
        arn = authInfo.getArn();
    }
    if (objectMD['owner-id'] === canonicalID && requesterIsNotUser) {
        return true;
    }
    // account is authorized if:
    // - requesttype is included in bucketOwnerActions and
    // - account is the bucket owner
    // - requester is account, not user
    if (constants.bucketOwnerActions.includes(requestType)
    && (bucketOwner === canonicalID)
    && requesterIsNotUser) {
        return true;
    }
    const aclPermission = checkObjectAcls(bucket, objectMD, requestType,
        canonicalID);
    const bucketPolicy = bucket.getBucketPolicy();
    if (!bucketPolicy) {
        return aclPermission;
    }
    const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
        canonicalID, arn, bucket.getOwner(), log);
    if (bucketPolicyPermission === 'explicitDeny') {
        return false;
    }
    return (aclPermission || (bucketPolicyPermission === 'allow'));
}

function _checkResource(resource, bucketArn) {
    if (resource === bucketArn) {
        return true;
    }
    if (resource.includes('/')) {
        const rSubs = resource.split('/');
        return rSubs[0] === bucketArn;
    }
    return false;
}

// the resources specified in the bucket policy should contain the bucket name
function validatePolicyResource(bucketName, policy) {
    const bucketArn = `arn:aws:s3:::${bucketName}`;

    return policy.Statement.every(s => {
        if (Array.isArray(s.Resource)) {
            return s.Resource.every(r => _checkResource(r, bucketArn));
        }
        if (typeof s.Resource === 'string') {
            return _checkResource(s.Resource, bucketArn);
        }
        return false;
    });
}

module.exports = {
    isBucketAuthorized,
    isObjAuthorized,
    checkBucketAcls,
    checkObjectAcls,
    validatePolicyResource,
};
<|endoftext|>


<commit_before>var ACTIVE_LOCALE = window.ACTIVE_LOCALE || 'en';
var Locale = require('../../locale/index');
var Polyglot = require('node-polyglot');
var polyglot = new Polyglot({
  locale: ACTIVE_LOCALE, // Needed for pluralize behaviour
  phrases: Locale[ACTIVE_LOCALE]
});
window._t = polyglot.t.bind(polyglot);

var AuthenticatedUser = require('./common/authenticated-user-model');

var authenticatedUser = new AuthenticatedUser();

authenticatedUser.on('change', function (model) {
  console.log(model);
});

authenticatedUser.fetch();
<commit_msg>Added scroll to fixed view.<commit_after>var Polyglot = require('node-polyglot');
var _ = require('underscore');
var $ = require('jquery');
var Locale = require('../../locale/index');
var AuthenticatedUser = require('./common/authenticated-user-model');
var UserModel = require('./data/user-model');
var ConfigModel = require('./data/config-model');
var UserSettingsView = require('./public-common/user-settings-view');
var ScrollToFix = require('./util/scroll-tofixed-view');

var ACTIVE_LOCALE = window.ACTIVE_LOCALE || 'en';
var polyglot = new Polyglot({
  locale: ACTIVE_LOCALE, // Needed for pluralize behaviour
  phrases: Locale[ACTIVE_LOCALE]
});
window._t = polyglot.t.bind(polyglot);

var configModel = new ConfigModel(
  _.defaults(
    {
      base_url: window.base_url
    },
    window.config
  )
);

$(function () {
  var scrollableHeader = new ScrollToFix({ // eslint-disable-line
    el: $('.js-Navmenu')
  });

  var authenticatedUser = new AuthenticatedUser();

  authenticatedUser.on('change', function (model) {
    var user = new UserModel(authenticatedUser.attributes, {
      configModel: configModel
    });

    var userSettingsView = new UserSettingsView({
      el: $('.js-user-settings'),
      model: user
    });
    userSettingsView.render();
  });

  authenticatedUser.fetch();
});
<|endoftext|>