<commit_before>(function($) {
	function GridManager() {
		var gridElementPrefix = "#grid_";
		var gridElementSuffix = " .grid";
		var pagerElementSuffix = " .pager";
		var filterTriggerElementSuffix = " .grid-header .filter_toggle";
		var deleteElementSuffix = ' .grid-header .delete_button';
		var createElementSuffix = ' .grid-header .create_button';

		var grids = [];
    var createdIds = [];
		var options = {
			editable: true,
			enableAddRow: false,
			enableCellNavigation: true,
			asyncEditorLoading: false,
			autoEdit: false,
			cellFlashingCssClass: "master_flashing"
		}

		function init() {
		}

		function getEditorForType(type){
			switch(type){
				case "string":
				return TextCellEditor;
				case "text":
				return LongTextCellEditor;
				case "datetime":
				return DateCellEditor2;
				default:
				return TextCellEditor;
			}
		}

		function createNewGrid(name, path, columns, states) {
			var gridElement = $(gridElementPrefix + name + gridElementSuffix);
      console.log(states)
			// Append editor attribute to columns
			for(var i in columns){
				if(columns[i].editable == true) {
					columns[i].editor = getEditorForType(columns[i].type);
					if(columns[i].type == "datetime") {
						columns[i].formatter = DateCellFormatter;
						columns[i].DateShowFormat = "yy-mm-dd";
					}
				}
			}

      // Set Loader
			var loader = new Slick.Data.RemoteModel(path, columns);
			// So we know who is the owner
			// loader.connectionManager.remoteModel = loader;

			// ------------------------- Create Grid ------------------------------------
			var grid = new Slick.Grid(gridElement, loader.data, columns, options);
			loader.setGrid(grid);

			// create loading indicator on the activity panel
			loader.setLoadingIndicator(createLoadingIndicator(gridElement));

			// Set Pager
			var pagerElement = $(gridElementPrefix + name + pagerElementSuffix);
			var pager = new Slick.Controls.Pager(loader, grid, pagerElement);

      // Set Filter
			var filterTriggerElement = $(gridElementPrefix + name + filterTriggerElementSuffix);
			var filterPanel = new Slick.FilterPanel(grid, loader, filterTriggerElement);

			// Set connection manager
			// var connectionManager = new ConnectionManager();

			// Set grid body height after rendering
			setGridBodyHeight(gridElement);
			grid.resizeCanvas();

			// Load the first page
			grid.onViewportChanged();

			var grid_object = {name: name, path: path, columns: columns, loader: loader, grid: grid, pager: pager, filterPanel: filterPanel};
			grid.store = grid_object;

			// Append necessary attributes to the grid
			var grid_attributes = {name: name, path: path, pager: pager, filterPanel: filterPanel};
      for(var attr in grid_attributes) {
        grid[attr] = grid_attributes[attr];
      }

      // delete old grid if exsisting, then add grid
			for(var i in grids){
				if(grid.name == grids[i].name){
					grids.splice(i, 1);
				}
			}
			grids.push(grid);

      // ------------------------------- customized methods for the grid ----------------------------－－－－－－－

			// get row by record.id
			grid.getRowByRecordId = function(id) {
			  var data = this.store.loader.data;
			  for(var i in data) {
			    if (data.hasOwnProperty(i) && i !== 'length' && data[i].id == id) { return this.getRowAt(i); };
			  }
			}

			// ------------------------------- register events on the grid ----------------------------------------
			// register grid events
			grid.onCellChange = function(currentRow, currentCell, item) {
				Requests.updateByAjax(this, item);
			};

			// handle multiple grids: select one,release previou one
			grid.onClick = function(currentRow, currentCell, item) {
				$.each(grids, function(){
					if (this.name != grid.name)
						this.setSelectedRows([]);
				})
			};

			// ------------------------------ register callbacks for handling grid states ------------------------
			GridStatesManager.onStateEvents(grid);

			// ------------------------------ button events -----------------------------------------

			// Delete along delete button
			var deleteElement = $(gridElementPrefix + name + deleteElementSuffix);
			deleteElement.click(function() {
				var ids = Ui.selectIds(grid);
				if (ids) {
				  Ui.deleteGrids(ids);
  			  return false;
				} else {
					alert("Please select more than one row first!");
				}
			});

			// Create action
			var createButtonElement = $(gridElementPrefix + name + createElementSuffix);
			createButtonElement.click(function() {
				Ui.openDialog(name);
			});
			// Click 'Create' button
			$('#' + name + '_submit').click(function() {
				Requests.createByAjax(grid, false);
			  return false;
			});
			// Click 'Create and Continue' button
			$('#' + name + '_submit_continue').click(function() {
				Requests.createByAjax(grid, true);
			  return false;
			});

		} // createNewGrid

		function createLoadingIndicator(gridElement) {
			var truncateThreshold = 35;
			var parent = gridElement.parent(".grid_container");
			var id = parent.attr("id");
			var title = $.trim(parent.find(".grid-header h2").text());
			if (title.length > truncateThreshold) {
				title = title.substring(0, truncateThreshold-2) + "..."
			}
			var indicators = $("#activity #indicators");

			// Remove init indicator if it exists.
			indicators.find("#init_menu_indicator").remove();

			var indicator = indicators.find(".loading_indicator#" + id);

			if (indicator.length == 0) {
				indicator = $(buildIndicatorHtml(id, title, "")).appendTo(indicators);
				// Init counter
				indicator.data("requestCount", 0);
			}

			return indicator;
		}

		function buildIndicatorHtml(id, title){
			return "

"+ title +"

"
		}

		function getGrid(name) {
			var theGrid = null;

			$.each(grids, function() {
				if (this.name == name)
				theGrid = this;
			});

			return theGrid;
		}

		function setGridBodyHeight(gridElement) {
		  var container = gridElement.parent(".grid_container");
		  var ch = container.height();
		  var hh = container.find(".grid-header").height();
	    var ph = container.find(".pager").height();
	    var gh = ch - hh - ph;
	    gridElement.css("height", gh - 1);
		}

		function resizeGrids() {
			$.each(grids, function() {
			  var gridElement = $(gridElementPrefix + this.name + gridElementSuffix);
			  setGridBodyHeight(gridElement);
				this.resizeCanvas();
			});
		}

		init();

		return {
			// properties
			"grids": grids,
			'createdIds': createdIds,

			// methods
			"createNewGrid": createNewGrid,
			"getGrid": getGrid,
			"resizeGrids": resizeGrids,
			"buildIndicatorHtml": buildIndicatorHtml
		};
	}

	$.extend(true, window, { GridManager: GridManager});
	})(jQuery);

	var gridManager = new GridManager();

	$(window).resize(function() { gridManager.resizeGrids(); });
	<commit_msg>Done the grid states restoring<commit_after>(function($) {
	function GridManager() {
		var gridElementPrefix = "#grid_";
		var gridElementSuffix = " .grid";
		var pagerElementSuffix = " .pager";
		var filterTriggerElementSuffix = " .grid-header .filter_toggle";
		var deleteElementSuffix = ' .grid-header .delete_button';
		var createElementSuffix = ' .grid-header .create_button';

		var grids = [];
    var createdIds = [];
		var options = {
			editable: true,
			enableAddRow: false,
			enableCellNavigation: true,
			asyncEditorLoading: false,
			autoEdit: false,
			cellFlashingCssClass: "master_flashing"
		}

		function init() {
		}

		function getEditorForType(type){
			switch(type){
				case "string":
				return TextCellEditor;
				case "text":
				return LongTextCellEditor;
				case "datetime":
				return DateCellEditor2;
				default:
				return TextCellEditor;
			}
		}

		function appendEditor(columns){
		  for(var i in columns){
				if(columns[i].editable == true) {
					columns[i].editor = getEditorForType(columns[i].type);
					if(columns[i].type == "datetime") {
						columns[i].formatter = DateCellFormatter;
						columns[i].DateShowFormat = "yy-mm-dd";
					}
				}
			}
		}

		function applyWidthAndOrderStates(columns, states){
		  var new_columns = [];

		  // find id column
		  for(var i in columns){
			  if (columns[i].id == "id"){
			    new_columns.push(columns[i]);
			    break;
			  }
			}
			// push other columns according to states
			for(var j in states.order){
			  for(var k in columns) {
			    // restore order
			    if(columns[k].id == states.order[j]){
			      // restore width
			      columns[k].width = states.width[columns[k].id]
			      new_columns.push(columns[k]);
			      break;
			    }
			  }
			}
			return new_columns;
		}

		function applySortingStates(loader, states) {
		  loader.setSort(states.sort["sortCol"], states.sort["sortDir"]);
		}

		function createNewGrid(name, path, columns, states) {
			var gridElement = $(gridElementPrefix + name + gridElementSuffix);

			// Append editor attribute to columns
			appendEditor(columns);

			// apply the width and order states to columns
		  columns = applyWidthAndOrderStates(columns, states);

      // Set Loader
			var loader = new Slick.Data.RemoteModel(path, columns);
			// So we know who is the owner
			// loader.connectionManager.remoteModel = loader;

			// ------------------------- Create Grid ------------------------------------
			var grid = new Slick.Grid(gridElement, loader.data, columns, options);
			loader.setGrid(grid);

			// create loading indicator on the activity panel
			loader.setLoadingIndicator(createLoadingIndicator(gridElement));

			// apply the sorting states to grid
		  applySortingStates(loader, states);

			// Set Pager
			var pagerElement = $(gridElementPrefix + name + pagerElementSuffix);
			var pager = new Slick.Controls.Pager(loader, grid, pagerElement);

      // Set Filter
			var filterTriggerElement = $(gridElementPrefix + name + filterTriggerElementSuffix);
			var filterPanel = new Slick.FilterPanel(grid, loader, filterTriggerElement);

			// Set connection manager
			// var connectionManager = new ConnectionManager();

			// Set grid body height after rendering
			setGridBodyHeight(gridElement);
			grid.resizeCanvas();

			// Load the first page
			grid.onViewportChanged();

      // FIX ME!!!!!!
      var grid_object = {name: name, path: path, columns: columns, loader: loader, grid: grid, pager: pager, filterPanel: filterPanel};
      grid.store = grid_object;

			// Append necessary attributes to the grid
			var grid_attributes = {name: name, loader: loader, path: path, pager: pager, filterPanel: filterPanel};
      for(var attr in grid_attributes) {
        grid[attr] = grid_attributes[attr];
      }

      // delete old grid if exsisting, then add grid
			for(var i in grids){
				if(grid.name == grids[i].name){
					grids.splice(i, 1);
				}
			}
			grids.push(grid);

      // ------------------------------- customized methods for the grid ----------------------------－－－－－－－

			// get row by record.id
			grid.getRowByRecordId = function(id) {
			  var data = this.store.loader.data;
			  for(var i in data) {
			    if (data.hasOwnProperty(i) && i !== 'length' && data[i].id == id) { return this.getRowAt(i); };
			  }
			}

			// ------------------------------- register events on the grid ----------------------------------------
			// register grid events
			grid.onCellChange = function(currentRow, currentCell, item) {
				Requests.updateByAjax(this, item);
			};

			// handle multiple grids: select one,release previou one
			grid.onClick = function(currentRow, currentCell, item) {
				$.each(grids, function(){
					if (this.name != grid.name)
						this.setSelectedRows([]);
				})
			};

			// ------------------------------ register callbacks for handling grid states ------------------------
			GridStatesManager.onStateEvents(grid);

			// ------------------------------ button events -----------------------------------------

			// Delete along delete button
			var deleteElement = $(gridElementPrefix + name + deleteElementSuffix);
			deleteElement.click(function() {
				var ids = Ui.selectIds(grid);
				if (ids) {
				  Ui.deleteGrids(ids);
  			  return false;
				} else {
					alert("Please select more than one row first!");
				}
			});

			// Create action
			var createButtonElement = $(gridElementPrefix + name + createElementSuffix);
			createButtonElement.click(function() {
				Ui.openDialog(name);
			});
			// Click 'Create' button
			$('#' + name + '_submit').click(function() {
				Requests.createByAjax(grid, false);
			  return false;
			});
			// Click 'Create and Continue' button
			$('#' + name + '_submit_continue').click(function() {
				Requests.createByAjax(grid, true);
			  return false;
			});

		} // createNewGrid

		function createLoadingIndicator(gridElement) {
			var truncateThreshold = 35;
			var parent = gridElement.parent(".grid_container");
			var id = parent.attr("id");
			var title = $.trim(parent.find(".grid-header h2").text());
			if (title.length > truncateThreshold) {
				title = title.substring(0, truncateThreshold-2) + "..."
			}
			var indicators = $("#activity #indicators");

			// Remove init indicator if it exists.
			indicators.find("#init_menu_indicator").remove();

			var indicator = indicators.find(".loading_indicator#" + id);

			if (indicator.length == 0) {
				indicator = $(buildIndicatorHtml(id, title, "")).appendTo(indicators);
				// Init counter
				indicator.data("requestCount", 0);
			}

			return indicator;
		}

		function buildIndicatorHtml(id, title){
			return "

"+ title +"

"
		}

		function getGrid(name) {
			var theGrid = null;

			$.each(grids, function() {
				if (this.name == name)
				theGrid = this;
			});

			return theGrid;
		}

		function setGridBodyHeight(gridElement) {
		  var container = gridElement.parent(".grid_container");
		  var ch = container.height();
		  var hh = container.find(".grid-header").height();
	    var ph = container.find(".pager").height();
	    var gh = ch - hh - ph;
	    gridElement.css("height", gh - 1);
		}

		function resizeGrids() {
			$.each(grids, function() {
			  var gridElement = $(gridElementPrefix + this.name + gridElementSuffix);
			  setGridBodyHeight(gridElement);
				this.resizeCanvas();
			});
		}

		init();

		return {
			// properties
			"grids": grids,
			'createdIds': createdIds,

			// methods
			"createNewGrid": createNewGrid,
			"getGrid": getGrid,
			"resizeGrids": resizeGrids,
			"buildIndicatorHtml": buildIndicatorHtml
		};
	}

	$.extend(true, window, { GridManager: GridManager});
	})(jQuery);

	var gridManager = new GridManager();

	$(window).resize(function() { gridManager.resizeGrids(); });
	<|endoftext|>


<commit_before>$(document).ready(function() {
	// Management User
	var dataTable = $('#manus-grid').DataTable( {
            "processing": true,
            "serverSide": true,
            "order": [[ 0, "desc" ]],
            "ajax":{
                url : site_url + 'tlhp/manus/get_all_user', // json data
                type: "post",
                error: function(){  // error handling
                    $(".data-grid-error").html("");
                    $("#data-grid").append('

No data found in the server

');
                    $("#data-grid_processing").css("display","none");
                }
            },

			"fnRowCallback": function( nRow, aData, iDisplayIndex ) {
			    $('td:eq(1)', nRow).html('' + aData[1] + '');
			    return nRow;
			  },

			"aoColumnDefs": [{
				"aTargets": [7],
				mRender: function ( data, type, row ) {
					//return '
 Edit    Delete
';
					return '
 Edit    Delete
';
				}
			}]
      });

 	// add tim
	$('#add-tim').click(function(event){
		event.preventDefault();
		console.log('tambah tim');
		var add_more = $('.add-more-tim').last().clone(true).show();
//			$('#noper-more').append($('.add-more-noper').show());
		$('#tim-more').append(add_more);
	});

 	// add tim oerpanjangan
	$('#add-tim-perpanjangan').click(function(event){
		event.preventDefault();
		console.log('tambah tim perpanjangan');
		var add_more = $('.add-more-tim-perpanjangan').last().clone(true).show();
//			$('#noper-more').append($('.add-more-noper').show());
		$('#tim-more').append(add_more);
	});

	// Add new tr rekomendasi
	$("#add-temuan").click(function(event){
		event.preventDefault();
		console.log('add new rekomen');
		var add_more_temuan = $('.temuan-tr').last().clone();
		$('.temuan-tr:last').after(add_more_temuan);
		$('.no-temuan:last').text(+($('.no-temuan:last').text()) + 1);
		//$('tr.rekomen-tr').last().after($(".hiddensit")).clone(true);
	});

	// Add new tr rekomendasi
	$("#add-rekomen").click(function(event){
		event.preventDefault();
		console.log('add new rekomen');
		var add_more_rekomen = $('.rekomen-tr').last().clone();
		$('.rekomen-tr:last').after(add_more_rekomen);
		$('.no-rekomen:last').text(+($('.no-rekomen:last').text()) + 1);
		//$('tr.rekomen-tr').last().after($(".hiddensit")).clone(true);
	});

	// Add new tr tindak lanjut
	$("#add-tl").click(function(event){
		event.preventDefault();
		console.log('add new tl');
		var add_more_rekomen = $('.tl-tr').last().clone();
		$('.tl-tr:last').after(add_more_rekomen);
		$('.no-tl:last').text(+($('.no-tl:last').text()) + 1);
		//$('tr.rekomen-tr').last().after($(".hiddensit")).clone(true);
	});

	// Save All KKLHP
	$('#kklhp').click(function() {
		$("#kklhp-form").submit();
	});

	// load user
	$('.user-modal').on('click', function(e){
		  e.preventDefault();
		  $('#userModal').modal('show').find('.modal-body').load($(this).attr('href'));
	});

});

function userEdit(id) {
	body = site_url + 'tlhp/manus/update_user/'+ id;
	$('#userModal').modal('show').find('.modal-body').load(body);
}<commit_msg>tlhp update<commit_after>$(document).ready(function() {
	// Management User
	var dataTable = $('#manus-grid').DataTable( {
            "processing": true,
            "serverSide": true,
            "order": [[ 0, "desc" ]],
            "ajax":{
                url : site_url + 'tlhp/manus/get_all_user', // json data
                type: "post",
                error: function(){  // error handling
                    $(".data-grid-error").html("");
                    $("#data-grid").append('

No data found in the server

');
                    $("#data-grid_processing").css("display","none");
                }
            },

			"fnRowCallback": function( nRow, aData, iDisplayIndex ) {
			    $('td:eq(1)', nRow).html('' + aData[1] + '');
			    return nRow;
			  },

			"aoColumnDefs": [{
				"aTargets": [7],
				mRender: function ( data, type, row ) {
					//return '
 Edit    Delete
';
					return '
 Edit    Delete
';
				}
			}]
      });

 	// add tim
	$('#add-tim').click(function(event){
		event.preventDefault();
		console.log('tambah tim');
		var add_more = $('.add-more-tim').last().clone(true).show();
//			$('#noper-more').append($('.add-more-noper').show());
		$('#tim-more').append(add_more);
	});

 	// add tim oerpanjangan
	$('#add-tim-perpanjangan').click(function(event){
		event.preventDefault();
		console.log('tambah tim perpanjangan');
		var add_more = $('.add-more-tim-perpanjangan').last().clone(true).show();
//			$('#noper-more').append($('.add-more-noper').show());
		$('#tim-more').append(add_more);
	});

	// Add new tr rekomendasi
	$("#add-temuan").click(function(event){
		event.preventDefault();
		console.log('add new rekomen');
		var add_more_temuan = $('.temuan-tr').last().clone();
		$('.temuan-tr:last').after(add_more_temuan);
		$('.no-temuan:last').text(+($('.no-temuan:last').text()) + 1);
		//$('tr.rekomen-tr').last().after($(".hiddensit")).clone(true);
	});

	// Add new tr rekomendasi
	$("#add-rekomen").click(function(event){
		event.preventDefault();
		console.log('add new rekomen');
		var add_more_rekomen = $('.rekomen-tr').last().clone();
		$('.rekomen-tr:last').after(add_more_rekomen);
		$('.no-rekomen:last').text(+($('.no-rekomen:last').text()) + 1);
		//$('tr.rekomen-tr').last().after($(".hiddensit")).clone(true);
	});

	// Add new tr tindak lanjut
	$("#add-tl").click(function(event){
		event.preventDefault();
		console.log('add new tl');
		var add_more_rekomen = $('.tl-tr').last().clone();
		$('.tl-tr:last').after(add_more_rekomen);
		$('.no-tl:last').text(+($('.no-tl:last').text()) + 1);
		//$('tr.rekomen-tr').last().after($(".hiddensit")).clone(true);
	});

	// Save All KKLHP
	$('#kklhp').click(function() {
		$("#kklhp-form").submit();
	});

	// load user
	$('.user-modal').on('click', function(e){
		  e.preventDefault();
		  $('#userModal').modal('show').find('.modal-body').load($(this).attr('href'));
	});

});

function userEdit(id) {
	body = site_url + 'tlhp/manus/update_user/'+ id;
	$('#userModal').modal('show').find('.modal-body').load(body);
}<|endoftext|>


<commit_before>const got = require('got');
const { isVersion, sortVersions } = require('../versioning')('pep440');
const url = require('url');
const is = require('@sindresorhus/is');

module.exports = {
  getDependency,
};

function normalizeName(input) {
  return input.toLowerCase().replace('-', '_');
}

async function getDependency(purl, config = {}) {
  const { fullname: depName } = purl;
  let hostUrl = 'https://pypi.org/pypi/';
  if (!is.empty(config.registryUrls)) {
    [hostUrl] = config.registryUrls;
  }
  const lookupUrl = url.resolve(hostUrl, `${depName}/json`);
  try {
    const dependency = {};
    const rep = await got(lookupUrl, {
      json: true,
    });
    const dep = rep && rep.body;
    if (!dep) {
      logger.debug({ depName }, 'pip package not found');
      return null;
    }
    if (
      !(dep.info && normalizeName(dep.info.name) === normalizeName(depName))
    ) {
      logger.warn(
        { lookupName: depName, returnedName: dep.info.name },
        'Returned name does not match with requested name'
      );
      return null;
    }
    if (dep.info && dep.info.home_page) {
      if (dep.info.home_page.startsWith('https://github.com')) {
        dependency.repositoryUrl = dep.info.home_page;
      } else {
        dependency.homepage = dep.info.home_page;
      }
    }
    dependency.releases = [];
    if (dep.releases) {
      const versions = Object.keys(dep.releases)
        .filter(isVersion)
        .sort(sortVersions);
      dependency.releases = versions.map(version => ({
        version,
        releaseTimestamp: (dep.releases[version][0] || {}).upload_time,
      }));
    }
    return dependency;
  } catch (err) {
    logger.info('pypi dependency not found: ' + depName);
    return null;
  }
}
<commit_msg>logs: log pypi lookupUrl if name mismatch<commit_after>const got = require('got');
const { isVersion, sortVersions } = require('../versioning')('pep440');
const url = require('url');
const is = require('@sindresorhus/is');

module.exports = {
  getDependency,
};

function normalizeName(input) {
  return input.toLowerCase().replace('-', '_');
}

async function getDependency(purl, config = {}) {
  const { fullname: depName } = purl;
  let hostUrl = 'https://pypi.org/pypi/';
  if (!is.empty(config.registryUrls)) {
    [hostUrl] = config.registryUrls;
  }
  const lookupUrl = url.resolve(hostUrl, `${depName}/json`);
  try {
    const dependency = {};
    const rep = await got(lookupUrl, {
      json: true,
    });
    const dep = rep && rep.body;
    if (!dep) {
      logger.debug({ depName }, 'pip package not found');
      return null;
    }
    if (
      !(dep.info && normalizeName(dep.info.name) === normalizeName(depName))
    ) {
      logger.warn(
        { lookupUrl, lookupName: depName, returnedName: dep.info.name },
        'Returned name does not match with requested name'
      );
      return null;
    }
    if (dep.info && dep.info.home_page) {
      if (dep.info.home_page.startsWith('https://github.com')) {
        dependency.repositoryUrl = dep.info.home_page;
      } else {
        dependency.homepage = dep.info.home_page;
      }
    }
    dependency.releases = [];
    if (dep.releases) {
      const versions = Object.keys(dep.releases)
        .filter(isVersion)
        .sort(sortVersions);
      dependency.releases = versions.map(version => ({
        version,
        releaseTimestamp: (dep.releases[version][0] || {}).upload_time,
      }));
    }
    return dependency;
  } catch (err) {
    logger.info('pypi dependency not found: ' + depName);
    return null;
  }
}
<|endoftext|>


<commit_before>/**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *  64-bit Unsigned Intergers
**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~**/

(function () {

  function uint(x) {
    return x | 0x0;
  }

  function ulong(x) {
    return [uint(x[0]), uint(x[1])];
  }

  /* uint functions */

  function uint_gt(x, y) {
    var a, b;
    a = (x >> 16) & 0xffff;
    b = (y >> 16) & 0xffff;
    return (a > b) || ((a === b) && ((x & 0xffff) > (y & 0xffff)));
  }

  function uint_lt(x, y) {
    var a, b;
    a = (x >> 16) & 0xffff;
    b = (y >> 16) & 0xffff;
    return (a < b) || ((a === b) && ((x & 0xffff) < (y & 0xffff)));
  }

  /* ulong functions */

  function ulong_and(x, y) {
    return [x[0] & y[0], x[1] & y[1]];
  }

  function ulong_or(x, y) {
    return [x[0] | y[0], x[1] | y[1]];
  }

  function ulong_xor(x, y) {
    return [x[0] ^ y[0], x[1] ^ y[1]];
  }

  function ulong_not(x, y) {
    return [~x[0], ~x[1]];
  }

  function ulong_shl(x, n) {
    var a = uint(x[0]),
        b = uint(x[1]),
        c = n >= 32 ? (b << (n - 32)) :
            n === 0 ? a : ((a << n) | (b >>> (32 - n))),
        d = n >= 32 ? 0x0 : (b << n);
    return ulong([c, d]);
  }

  function ulong_shr(x, n) {
    var a = uint(x[0]),
        b = uint(x[1]),
        c = n >= 32 ? 0x0 : (a >>> n),
        d = n >= 32 ? (a >>> (n - 32)) :
            n === 0 ? b : ((a << (32 - n)) | (b >>> n));
    return ulong([c, d]);
  }

  function ulong_rotl(x, n) {
    return ulong_or(ulong_shr(x, (64 - n)), ulong_shl(x, n));
  }

  function ulong_rotr(x, n) {
    return ulong_or(ulong_shr(x, n), ulong_shl(x, (64 - n)));
  }

  function ulong_add(x, y) {
    x = ulong(x);
    y = ulong(y);
    var b = x[1] + y[1],
        a = x[0] + y[0] + (uint_lt(b, x[1]) ? 0x1 : 0x0);
    return ulong([a, b]);
  }

  function ulong_subt(x, y) {
    x = ulong(x);
    y = ulong(y);
    var b = x[1] - y[1],
        a = x[0] - y[1] - (uint_gt(b, x[1]) ? 0x1 : 0x0);
    return ulong([a, b]);
  }

  function ulong_mult(x, y) {
    var i, a = ulong([0x0, 0x0]);
    for (i = 0; i < 64; i += 1) {
      if (ulong_shr(y, i)[1] & 0x1) {
        a = ulong_add(a, ulong_shl(x, i));
      }
    }
    return ulong(a);
  }

  /* expose */

  this.Digest.ulong = {
    ulong : ulong,
    and   : ulong_and,
    or    : ulong_or,
    xor   : ulong_xor,
    not   : ulong_not,
    rotl  : ulong_rotl,
    rotr  : ulong_rotr,
    shl   : ulong_shl,
    shr   : ulong_shr,
    add   : ulong_add,
    subt  : ulong_subt,
    mult  : ulong_mult
  };

}());
<commit_msg>Revise Digest.ulong functions to reduce runtime.<commit_after>/**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *  64-bit Unsigned Intergers
**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~**/

(function () {

  function ulong(x) {
    return [(x[0] | 0x0), (x[1] | 0x0)];
    //return [uint(x[0]), uint(x[1])];
  }

  /* uint functions */

  function uint_gt(x, y) {
    var a, b;
    a = (x >> 16) & 0xffff;
    b = (y >> 16) & 0xffff;
    return (a > b) || ((a === b) && ((x & 0xffff) > (y & 0xffff)));
  }

  function uint_lt(x, y) {
    var a, b;
    a = (x >> 16) & 0xffff;
    b = (y >> 16) & 0xffff;
    return (a < b) || ((a === b) && ((x & 0xffff) < (y & 0xffff)));
  }

  /* ulong functions */

  function ulong_and(x, y) {
    return [x[0] & y[0], x[1] & y[1]];
  }

  function ulong_or(x, y) {
    return [x[0] | y[0], x[1] | y[1]];
  }

  function ulong_xor(x, y) {
    return [x[0] ^ y[0], x[1] ^ y[1]];
  }

  function ulong_not(x, y) {
    return [~x[0], ~x[1]];
  }

  function ulong_shl(x, n) {
    var a = x[0] | 0x0,
        b = x[1] | 0x0,
        c = n >= 32 ? (b << (n - 32)) :
            n === 0 ? a : ((a << n) | (b >>> (32 - n))),
        d = n >= 32 ? 0x0 : (b << n);
    return ulong([c, d]);
  }

  function ulong_shr(x, n) {
    var a = x[0] | 0x0,
        b = x[1] | 0x0,
        c = n >= 32 ? 0x0 : (a >>> n),
        d = n >= 32 ? (a >>> (n - 32)) :
            n === 0 ? b : ((a << (32 - n)) | (b >>> n));
    return ulong([c, d]);
  }

  function ulong_rotl(x, n) {
    return ulong_or(ulong_shr(x, (64 - n)), ulong_shl(x, n));
  }

  function ulong_rotr(x, n) {
    return ulong_or(ulong_shr(x, n), ulong_shl(x, (64 - n)));
  }

  function ulong_add(x, y) {
    var b = x[1] + y[1],
        a = x[0] + y[0] + (uint_lt(b, x[1]) ? 0x1 : 0x0);
    return ulong([a, b]);
  }

  function ulong_subt(x, y) {
    var b = x[1] - y[1],
        a = x[0] - y[1] - (uint_gt(b, x[1]) ? 0x1 : 0x0);
    return ulong([a, b]);
  }

  function ulong_mult(x, y) {
    var i, a = [0x0, 0x0];
    for (i = 0; i < 64; i += 1) {
      if (ulong_shr(y, i)[1] & 0x1) {
        a = ulong_add(a, ulong_shl(x, i));
      }
    }
    return ulong(a);
  }

  /* expose */

  this.Digest.ulong = {
    ulong : ulong,
    and   : ulong_and,
    or    : ulong_or,
    xor   : ulong_xor,
    not   : ulong_not,
    rotl  : ulong_rotl,
    rotr  : ulong_rotr,
    shl   : ulong_shl,
    shr   : ulong_shr,
    add   : ulong_add,
    subt  : ulong_subt,
    mult  : ulong_mult
  };

}());
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Generic Mustang-like query-language parser generator.
 *
 * key:value                  key contains "value"
 * key=value                  key exactly matches "value"
 * key:value1,value2          key contains "value1" OR "value2"
 * key1:value key2:value      key1 contains value AND key2 contains "value"
 * key1:value AND key2:value  "
 * key1:value OR key2:value   key1 contains value OR key2 contains "value"
 * key1:value or key2:value   "
 * (expr)                     groups expression
 * -expr                      not expression, ie. -pri:1
 * has:key                    key has a value
 * is:key                     key is a boolean TRUE value
 * key>value                  key is greater than value
 * key-after:value            "
 * key<value                  key is less than value
 * key-before:value           "
 * date:YY/MM/DD              date specified
 * date:today                 date of today
 * date-after:today-7         date newer than 7 days ago
 * key:me                     key is the current user
 */
var QueryParserFactory = function(model) {
  var g = {
    __proto__: grammar,

    START: sym('query'),

    query: sym('or'),

    or: repeat(sym('and'), literal_ic(' OR '), 1),

    and: repeat(
      sym('expr'),
      alt(literal_ic('AND '), not(literal_ic(' OR'), ' ')),
      1),

    expr: alt(
      sym('paren'),
      sym('negate'),
      sym('has'),
      sym('is'),
      sym('equals'),
      sym('before'),
      sym('after'),
      sym('id')
    ),

    paren: seq1(1, '(', sym('query'), ')'),

    negate: seq('-', sym('expr')),

    id: sym('number'),

    has: seq(literal_ic('has:'), sym('fieldname')),

    is: seq(literal_ic('is:'), sym('fieldname')),

    equals: seq(sym('fieldname'), alt(':', '='), sym('valueList')),

    // TODO: merge with 'equals'
    before: seq(sym('fieldname'), alt('<','-before:'), sym('value')),

    // TODO: merge with 'equals'
    after: seq(sym('fieldname'), alt('>', '-after:'), sym('value')),

    value: alt(
      sym('me'),
      sym('date'),
      sym('string'),
      sym('number')),

    valueList: repeat(sym('value'), ',', 1),

    me: seq(literal_ic('me'), lookahead(not(sym('char')))),

    date: alt(
      sym('literal date'),
      sym('relative date')),

    'literal date': seq(sym('number'), '/', sym('number'), '/', sym('number')),

    'relative date': seq(literal_ic('today'), optional(seq('-', sym('number')))),

    string: alt(
      sym('word'),
      sym('quoted string')),

    'quoted string': str(seq1(1, '"', repeat(alt(literal('\\"', '"'), notChar('"'))), '"')),

    word: str(plus(sym('char'))),

    char: alt(range('a','z'), range('A', 'Z'), range('0', '9'), '-', '^', '_', '@', '%', '.'),

    number: str(plus(range('0', '9')))
  };

  var fields = [];

  for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var prop = model.properties[i];
    fields.push(literal_ic(prop.name, prop));
  }

  // Aliases
  for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var prop = model.properties[i];

    for ( var j = 0 ; j < prop.aliases.length ; j++ )
      if ( prop.aliases[j] ) fields.push(literal_ic(prop.aliases[j], prop));
  }

  // ShortNames
  for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var prop = model.properties[i];

    if ( prop.shortName ) fields.push(literal_ic(prop.shortName, prop));
  }

  fields.sort(function(a, b) {
    var d = a.length - b.length;

    if ( d !== 0 ) return d;

    if ( a == b ) return 0;

    return a < b ? 1 : -1;
  });

  g.fieldname = alt.apply(null, fields);

  g.addActions({
    id: function(v) { return EQ(model.ID, v); },

    or: function(v) { return OR.apply(OR, v); },

    and: function(v) { return AND.apply(AND, v); },

    negate: function(v) { return NOT(v[1]); },

    number: function(v) { return parseInt(v); },

    me: function() { return this.ME || this.X.ME || ""; },

    has: function(v) { return NEQ(v[1], ''); },

    is: function(v) { return EQ(v[1], TRUE); },

    before: function(v) { return LT(v[0], v[2]); },

    after: function(v) { return GT(v[0], v[2]); },

    equals: function(v) {
      // Always treat an OR'ed value list and let the partial evalulator
      // simplify it when it isn't.

      var prop    = v[0];
      var values  = v[2];
      var isInt   = IntProperty.isInstance(prop);
      var isNum   = isInt || FloatProperty.isInstance(prop);

      if ( isNum ) {
        for ( var i = 0 ; i < values.length ; i++ )
          values[i] = isInt ? parseInt(values[i]) : parseFloat(values[i]);
      }

      return ( v[1] === '=' || isNum ) ?
        IN(v[0], values) :
        ContainedInICExpr.create({arg1: compile_(prop), arg2: values}) ;
    },

    'literal date': function(v) { return new Date(v[0], v[2]-1, v[4]); },

    'relative date': function(v) {
      var d = new Date();
      if ( v[1] ) d.setDate(d.getDate() - v[1][1]);
      return d;
    }
  });

  return g;
};
<commit_msg>QueryParser now handles | as well as OR<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Generic Mustang-like query-language parser generator.
 *
 * key:value                  key contains "value"
 * key=value                  key exactly matches "value"
 * key:value1,value2          key contains "value1" OR "value2"
 * key1:value key2:value      key1 contains value AND key2 contains "value"
 * key1:value AND key2:value  "
 * key1:value OR key2:value   key1 contains value OR key2 contains "value"
 * key1:value or key2:value   "
 * (expr)                     groups expression
 * -expr                      not expression, ie. -pri:1
 * has:key                    key has a value
 * is:key                     key is a boolean TRUE value
 * key>value                  key is greater than value
 * key-after:value            "
 * key<value                  key is less than value
 * key-before:value           "
 * date:YY/MM/DD              date specified
 * date:today                 date of today
 * date-after:today-7         date newer than 7 days ago
 * key:me                     key is the current user
 */
var QueryParserFactory = function(model) {
  var g = {
    __proto__: grammar,

    START: sym('query'),

    query: sym('or'),

    or: repeat(sym('and'), alt(literal_ic(' OR '), literal(' | ')), 1),

    and: repeat(
      sym('expr'),
      alt(literal_ic('AND '), not(alt(literal_ic(' OR'), literal(' |')), ' ')),
      1),

    expr: alt(
      sym('paren'),
      sym('negate'),
      sym('has'),
      sym('is'),
      sym('equals'),
      sym('before'),
      sym('after'),
      sym('id')
    ),

    paren: seq1(1, '(', sym('query'), ')'),

    negate: seq('-', sym('expr')),

    id: sym('number'),

    has: seq(literal_ic('has:'), sym('fieldname')),

    is: seq(literal_ic('is:'), sym('fieldname')),

    equals: seq(sym('fieldname'), alt(':', '='), sym('valueList')),

    // TODO: merge with 'equals'
    before: seq(sym('fieldname'), alt('<','-before:'), sym('value')),

    // TODO: merge with 'equals'
    after: seq(sym('fieldname'), alt('>', '-after:'), sym('value')),

    value: alt(
      sym('me'),
      sym('date'),
      sym('string'),
      sym('number')),

    valueList: repeat(sym('value'), ',', 1),

    me: seq(literal_ic('me'), lookahead(not(sym('char')))),

    date: alt(
      sym('literal date'),
      sym('relative date')),

    'literal date': seq(sym('number'), '/', sym('number'), '/', sym('number')),

    'relative date': seq(literal_ic('today'), optional(seq('-', sym('number')))),

    string: alt(
      sym('word'),
      sym('quoted string')),

    'quoted string': str(seq1(1, '"', repeat(alt(literal('\\"', '"'), notChar('"'))), '"')),

    word: str(plus(sym('char'))),

    char: alt(range('a','z'), range('A', 'Z'), range('0', '9'), '-', '^', '_', '@', '%', '.'),

    number: str(plus(range('0', '9')))
  };

  var fields = [];

  for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var prop = model.properties[i];
    fields.push(literal_ic(prop.name, prop));
  }

  // Aliases
  for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var prop = model.properties[i];

    for ( var j = 0 ; j < prop.aliases.length ; j++ )
      if ( prop.aliases[j] ) fields.push(literal_ic(prop.aliases[j], prop));
  }

  // ShortNames
  for ( var i = 0 ; i < model.properties.length ; i++ ) {
    var prop = model.properties[i];

    if ( prop.shortName ) fields.push(literal_ic(prop.shortName, prop));
  }

  fields.sort(function(a, b) {
    var d = a.length - b.length;

    if ( d !== 0 ) return d;

    if ( a == b ) return 0;

    return a < b ? 1 : -1;
  });

  g.fieldname = alt.apply(null, fields);

  g.addActions({
    id: function(v) { return EQ(model.ID, v); },

    or: function(v) { return OR.apply(OR, v); },

    and: function(v) { return AND.apply(AND, v); },

    negate: function(v) { return NOT(v[1]); },

    number: function(v) { return parseInt(v); },

    me: function() { return this.ME || this.X.ME || ""; },

    has: function(v) { return NEQ(v[1], ''); },

    is: function(v) { return EQ(v[1], TRUE); },

    before: function(v) { return LT(v[0], v[2]); },

    after: function(v) { return GT(v[0], v[2]); },

    equals: function(v) {
      // Always treat an OR'ed value list and let the partial evalulator
      // simplify it when it isn't.

      var prop    = v[0];
      var values  = v[2];
      var isInt   = IntProperty.isInstance(prop);
      var isNum   = isInt || FloatProperty.isInstance(prop);

      if ( isNum ) {
        for ( var i = 0 ; i < values.length ; i++ )
          values[i] = isInt ? parseInt(values[i]) : parseFloat(values[i]);
      }

      return ( v[1] === '=' || isNum ) ?
        IN(v[0], values) :
        ContainedInICExpr.create({arg1: compile_(prop), arg2: values}) ;
    },

    'literal date': function(v) { return new Date(v[0], v[2]-1, v[4]); },

    'relative date': function(v) {
      var d = new Date();
      if ( v[1] ) d.setDate(d.getDate() - v[1][1]);
      return d;
    }
  });

  return g;
};
<|endoftext|>


<commit_before>yaml = require('js-yaml') ,
utils = require('./utils'),
util = require('util'),
docker = require(./lib/docker')

function generate(data) {
	return yaml.dump(data)
}

function getContainerID(path, service, callback) {
	return utils.cmd('cd '+path+'; docker-compose ps -q '+ service,function(err, data) {

			if(err)
				callback(err, data)
			else {
				var containerID = data
				//Replace newlines
				containerID = containerID.replace(/(\r\n|\n|\r)/gm,"");
				callback(null,containerID)
			}

	})

}

//Call docker-compose up in path and returns the process pid
function up(path, callback) {

	return utils.cmd('cd '+path+'; docker-compose  up -d', callback)

}
//Call docker-compose down in path and returns the process pid
function down(path, callback) {

	return utils.cmd('cd '+path+'; docker-compose down', callback)

}

function cp(path, service, src, dst, callback) {

		getContainerID(path, service, function(err, data) {
			//No err : get containerID = data
			if(!err)
			{
				var containerID = data
				//Cal docker exec
				return docker.cp(containerID, src, dst, callback)
			}
			else   {
			callback(err)
			return null
			}

		})

}

function exec(path, service, command, callback, paramsInput) {

	var paramsProto = {
		detached: false
	}

	//Get the params
	var params = _.extend({}, paramsProto, paramsInput)

	if(!params.detached)
		return utils.cmd('cd '+path+'; docker-compose exec '+ service + ' ' +command, callback)

	//Workaround for detached mode docker-compose
	else {
		getContainerID(path, service, function(err, data) {
			//No err : get containerID = data
			if(!err)
			{

				var containerID = data
				//Cal docker exec
				return docker.exec(containerID, command, callback, params)

			}
			else callback(err)

		})
	//	return cmd.get('cd '+path+'; docker-compose exec '+ service + ' ' +command, callback)
		}
}

exports.generate = generate
exports.up = up
exports.down = down
exports.exec = exec
exports.cp = cp
exports.getContainerID = getContainerID
<commit_msg>update<commit_after>yaml = require('js-yaml') ,
utils = require('./utils'),
util = require('util'),
docker = require('./docker')

function generate(data) {
	return yaml.dump(data)
}

function getContainerID(path, service, callback) {
	return utils.cmd('cd '+path+'; docker-compose ps -q '+ service,function(err, data) {

			if(err)
				callback(err, data)
			else {
				var containerID = data
				//Replace newlines
				containerID = containerID.replace(/(\r\n|\n|\r)/gm,"");
				callback(null,containerID)
			}

	})

}

//Call docker-compose up in path and returns the process pid
function up(path, callback) {

	return utils.cmd('cd '+path+'; docker-compose  up -d', callback)

}
//Call docker-compose down in path and returns the process pid
function down(path, callback) {

	return utils.cmd('cd '+path+'; docker-compose down', callback)

}

function cp(path, service, src, dst, callback) {

		getContainerID(path, service, function(err, data) {
			//No err : get containerID = data
			if(!err)
			{
				var containerID = data
				//Cal docker exec
				return docker.cp(containerID, src, dst, callback)
			}
			else   {
			callback(err)
			return null
			}

		})

}

function exec(path, service, command, callback, paramsInput) {

	var paramsProto = {
		detached: false
	}

	//Get the params
	var params = _.extend({}, paramsProto, paramsInput)

	if(!params.detached)
		return utils.cmd('cd '+path+'; docker-compose exec '+ service + ' ' +command, callback)

	//Workaround for detached mode docker-compose
	else {
		getContainerID(path, service, function(err, data) {
			//No err : get containerID = data
			if(!err)
			{

				var containerID = data
				//Cal docker exec
				return docker.exec(containerID, command, callback, params)

			}
			else callback(err)

		})
	//	return cmd.get('cd '+path+'; docker-compose exec '+ service + ' ' +command, callback)
		}
}

exports.generate = generate
exports.up = up
exports.down = down
exports.exec = exec
exports.cp = cp
exports.getContainerID = getContainerID
<|endoftext|>


<commit_before>/**
 * This file creates a node.js Stream that re-writes chunks of HTML on-the-fly so that all
 * non-relative URLS are prefixed with the given string.
 *
 * For example, If you set the config.prefix to '/proxy/' and pass in this chunk of html:
 *   link to example.com
 * It would output this:
 *   link to example.com
 *
 * It buffers a small amount of text from the end of each chunk to ensure that it properly
 * handles links that are split between two chunks (packets).
 */

var URL = require('url');
var Transform = require('stream').Transform;
var contentTypes = require('./content-types.js');
var debug = require('debug')('unblocker:domain-prefixer');

function domainPrefixer(config) {
    var re_abs_url = /("|'|=|url\(\s*|url=| )(https?:)\/\/((?:[A-Z0-9-]{1,63}\.){1,125}[A-Z]{2,63})/ig, // "http:, 'http:, =http:, or url( http:, also matches https versions
        re_rel_proto = /("|'|=|url\(\s*|url=| )(\/\/((?:[A-Z0-9-]{1,63}\.){1,125}[A-Z]{2,63}))/ig, // matches //site.com style urls where the protocol is auto-sensed
        // no need to match relative or root-relative urls - those will work without modification

        // partial's dont cause anything to get changed, they just cause last few characters to be buffered and checked with the next batch
        re_html_partial = /((url\(\s*)?\s[^\s]+\s*)$/, // capture the last two "words" and any space after them handles chunks ending in things like `<a href=` and `background-image: url( ` or `url h`

        // things that shouldn't be proxied
        // (in order to keep this a little bit simpler, the initial regex proxies it, and then the second one unproxies it)
        // matches broken xmlns attributes like xmlns="http://h-t-t-p.www.w3.org.unblocker/1999/xhtml" and
        // xmlns:og="http://h-t-t-p.ogp.me.unblocker/ns#"
        re_proxied_xmlns = new RegExp('(xmlns(?::[a-z]+)?=")http:\/\/(h-t-t-p(?:-s)?\\.)((?:[A-Z0-9-]{1,63}\\.){1,125}[A-Z]{2,63})\\.' + config.domain.replace(/\./g, '\\.'), 'ig'),
        // This one is special
        re_proxied_doctype = new RegExp('http:\/\/(h-t-t-p(?:-s)?\\.)((?:[A-Z0-9-]{1,63}\\.){1,125}[A-Z]{2,63})\\.' + config.domain.replace(/\./g, '\\.'), 'i');
        // re_proxied_xmlns = new RegExp('(xmlns(:[a-z]+)?=")' + config.prefix, 'ig'),
        // re_proxied_doctype = new RegExp('(<!DOCTYPE[^>]+")' + config.prefix, 'i');

    function rewriteDomains(chunk, uri, prefix) {

        // first upgrade // links to regular http/https links because otherwise they look like root-relative (/whatever.html) links
        chunk = chunk.replace(re_rel_proto, "$1" + uri.protocol + "$2");
        // next replace urls that are relative to the root of the domain (/whatever.html) because this is how proxied urls look
        // chunk = chunk.replace(re_rel_root, "$1" + uri.protocol + "//" + uri.host + "$3");
        // last replace any complete urls
        //chunk = chunk.replace(re_abs_url, "$1" + prefix + "$2");
        chunk = chunk.replace(re_abs_url, function(match, r1, r2, r3) {
            return r1 + 'http://' + (r2 === 'http:' ? 'h-t-t-p.' : 'h-t-t-p-s.') + r3 + '.' + config.domain;
        });

        // fix xmlns attributes that were broken because they contained urls.
        // (JS RegExp doesn't support negative lookbehind, so breaking and then fixing is simpler than trying to not break in the first place)
        chunk = chunk.replace(re_proxied_xmlns, function(match, m1, m2, m3) {
            return m1 + (m2 === 'h-t-t-p.' ? 'http://' : 'https://') + m3;
        });

        // We do it this way to improve performance compared to a search for <!DOCTYPE
        // Test with a regex replacement only showed a delay of 600 ms...
        if(chunk.toUpperCase().indexOf('<!DOCTYPE') !== -1 && chunk.toUpperCase().indexOf('>', chunk.indexOf('<!DOCTYPE') + 10) !== -1) {
            var indexOfDoctype = chunk.toUpperCase().indexOf('<!DOCTYPE') + 10;
            chunk = chunk.substring(0, indexOfDoctype) +
                    chunk.substring(indexOfDoctype, chunk.indexOf('>', indexOfDoctype)).replace(re_proxied_doctype, function(match, m1, m2) {
                        return (m1 === 'h-t-t-p.' ? 'http://' : 'https://') + m2;
                    }) +
                    chunk.substring(chunk.indexOf('>', indexOfDoctype));
        }

        return chunk;
    }

    function createStream(uri) {

        // sometimes a chunk will end in data that may need to be modified, but it is impossible to tell
        // in that case, buffer the end and prepend it to the next chunk
        var chunk_remainder;

        return new Transform({
            decodeStrings: false,

            transform: function (chunk, encoding, next) {
                chunkData.amount++;
                chunk = chunk.toString();
                console.log(chunk.length);
                if (chunk_remainder) {
                    chunk = chunk_remainder + chunk;
                    chunk_remainder = undefined;
                }

                // second, check if any urls are partially present in the end of the chunk,
                // and buffer the end of the chunk if so; otherwise pass it along
                var partial_hits = chunk.match(re_html_partial);
                if (partial_hits && partial_hits[1]) {
                    var snip = partial_hits[1].length;
                    chunk_remainder = chunk.substr(-1 * snip);
                    chunk = chunk.substr(0, chunk.length - snip);
                }

                chunk = rewriteDomains(chunk, uri, config.prefix);

                this.push(chunk);
                next();
            },

            flush: function(done) {
                chunkData.amount++;
                // if we buffered a bit of text but we're now at the end of the data, then apparently
                // it wasn't a url - send it along

                if (chunk_remainder) {
                    this.push(rewriteDomains(chunk_remainder, uri, config.prefix, chunkData));
                    chunk_remainder = undefined;
                }
                done();
            }
        });
    }

    function prefixUrls(data) {
        if (contentTypes.shouldProcess(config, data)) {
            var uri = URL.parse(data.url);
            debug('prefixing all urls with %s', config.prefix);
            data.stream = data.stream.pipe(createStream(uri));
        }
    }

    prefixUrls.rewriteDomains = rewriteDomains; // for testing
    prefixUrls.createStream = createStream;

    return prefixUrls;
}

module.exports = domainPrefixer;
<commit_msg>Removed debug code<commit_after>/**
 * This file creates a node.js Stream that re-writes chunks of HTML on-the-fly so that all
 * non-relative URLS are prefixed with the given string.
 *
 * For example, If you set the config.prefix to '/proxy/' and pass in this chunk of html:
 *   link to example.com
 * It would output this:
 *   link to example.com
 *
 * It buffers a small amount of text from the end of each chunk to ensure that it properly
 * handles links that are split between two chunks (packets).
 */

var URL = require('url');
var Transform = require('stream').Transform;
var contentTypes = require('./content-types.js');
var debug = require('debug')('unblocker:domain-prefixer');

function domainPrefixer(config) {
    var re_abs_url = /("|'|=|url\(\s*|url=| )(https?:)\/\/((?:[A-Z0-9-]{1,63}\.){1,125}[A-Z]{2,63})/ig, // "http:, 'http:, =http:, or url( http:, also matches https versions
        re_rel_proto = /("|'|=|url\(\s*|url=| )(\/\/((?:[A-Z0-9-]{1,63}\.){1,125}[A-Z]{2,63}))/ig, // matches //site.com style urls where the protocol is auto-sensed
        // no need to match relative or root-relative urls - those will work without modification

        // partial's dont cause anything to get changed, they just cause last few characters to be buffered and checked with the next batch
        re_html_partial = /((url\(\s*)?\s[^\s]+\s*)$/, // capture the last two "words" and any space after them handles chunks ending in things like `<a href=` and `background-image: url( ` or `url h`

        // things that shouldn't be proxied
        // (in order to keep this a little bit simpler, the initial regex proxies it, and then the second one unproxies it)
        // matches broken xmlns attributes like xmlns="http://h-t-t-p.www.w3.org.unblocker/1999/xhtml" and
        // xmlns:og="http://h-t-t-p.ogp.me.unblocker/ns#"
        re_proxied_xmlns = new RegExp('(xmlns(?::[a-z]+)?=")http:\/\/(h-t-t-p(?:-s)?\\.)((?:[A-Z0-9-]{1,63}\\.){1,125}[A-Z]{2,63})\\.' + config.domain.replace(/\./g, '\\.'), 'ig'),
        // This one is special
        re_proxied_doctype = new RegExp('http:\/\/(h-t-t-p(?:-s)?\\.)((?:[A-Z0-9-]{1,63}\\.){1,125}[A-Z]{2,63})\\.' + config.domain.replace(/\./g, '\\.'), 'i');
        // re_proxied_xmlns = new RegExp('(xmlns(:[a-z]+)?=")' + config.prefix, 'ig'),
        // re_proxied_doctype = new RegExp('(<!DOCTYPE[^>]+")' + config.prefix, 'i');

    function rewriteDomains(chunk, uri, prefix) {

        // first upgrade // links to regular http/https links because otherwise they look like root-relative (/whatever.html) links
        chunk = chunk.replace(re_rel_proto, "$1" + uri.protocol + "$2");
        // next replace urls that are relative to the root of the domain (/whatever.html) because this is how proxied urls look
        // chunk = chunk.replace(re_rel_root, "$1" + uri.protocol + "//" + uri.host + "$3");
        // last replace any complete urls
        //chunk = chunk.replace(re_abs_url, "$1" + prefix + "$2");
        chunk = chunk.replace(re_abs_url, function(match, r1, r2, r3) {
            return r1 + 'http://' + (r2 === 'http:' ? 'h-t-t-p.' : 'h-t-t-p-s.') + r3 + '.' + config.domain;
        });

        // fix xmlns attributes that were broken because they contained urls.
        // (JS RegExp doesn't support negative lookbehind, so breaking and then fixing is simpler than trying to not break in the first place)
        chunk = chunk.replace(re_proxied_xmlns, function(match, m1, m2, m3) {
            return m1 + (m2 === 'h-t-t-p.' ? 'http://' : 'https://') + m3;
        });

        // We do it this way to improve performance compared to a search for <!DOCTYPE
        // Test with a regex replacement only showed a delay of 600 ms...
        if(chunk.toUpperCase().indexOf('<!DOCTYPE') !== -1 && chunk.toUpperCase().indexOf('>', chunk.indexOf('<!DOCTYPE') + 10) !== -1) {
            var indexOfDoctype = chunk.toUpperCase().indexOf('<!DOCTYPE') + 10;
            chunk = chunk.substring(0, indexOfDoctype) +
                    chunk.substring(indexOfDoctype, chunk.indexOf('>', indexOfDoctype)).replace(re_proxied_doctype, function(match, m1, m2) {
                        return (m1 === 'h-t-t-p.' ? 'http://' : 'https://') + m2;
                    }) +
                    chunk.substring(chunk.indexOf('>', indexOfDoctype));
        }

        return chunk;
    }

    function createStream(uri) {

        // sometimes a chunk will end in data that may need to be modified, but it is impossible to tell
        // in that case, buffer the end and prepend it to the next chunk
        var chunk_remainder;

        return new Transform({
            decodeStrings: false,

            transform: function (chunk, encoding, next) {
                chunk = chunk.toString();
                if (chunk_remainder) {
                    chunk = chunk_remainder + chunk;
                    chunk_remainder = undefined;
                }

                // second, check if any urls are partially present in the end of the chunk,
                // and buffer the end of the chunk if so; otherwise pass it along
                var partial_hits = chunk.match(re_html_partial);
                if (partial_hits && partial_hits[1]) {
                    var snip = partial_hits[1].length;
                    chunk_remainder = chunk.substr(-1 * snip);
                    chunk = chunk.substr(0, chunk.length - snip);
                }

                chunk = rewriteDomains(chunk, uri, config.prefix);

                this.push(chunk);
                next();
            },

            flush: function(done) {
                // if we buffered a bit of text but we're now at the end of the data, then apparently
                // it wasn't a url - send it along

                if (chunk_remainder) {
                    this.push(rewriteDomains(chunk_remainder, uri, config.prefix));
                    chunk_remainder = undefined;
                }
                done();
            }
        });
    }

    function prefixUrls(data) {
        if (contentTypes.shouldProcess(config, data)) {
            var uri = URL.parse(data.url);
            debug('prefixing all urls with %s', config.prefix);
            data.stream = data.stream.pipe(createStream(uri));
        }
    }

    prefixUrls.rewriteDomains = rewriteDomains; // for testing
    prefixUrls.createStream = createStream;

    return prefixUrls;
}

module.exports = domainPrefixer;
<|endoftext|>


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

  // Define global variables that need some kind of initialization in case they're missing from Auth0
  var groups = user.groups || [];

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

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

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

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

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

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

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

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

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

  // Process the access cache decision
  function access_decision(access_rules, access_file_conf) {
    for (var i=0; i<access_rules.length; i++) {
      var app = access_rules[i].application;

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

      if (app.client_id && (app.client_id.indexOf(context.clientID) >= 0)) {

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

        // AAI (AUTHENTICATOR ASSURANCE INDICATOR) REQUIREMENTS
        // Defauts to MEDIUM for all apps which do not have this set in access file
        var aai = app.aai || "MEDIUM";
        // User.aai is set in another rule (rules/aai.js)

        // Mapping logic and verification
        // Ex: our mapping says 2FA for MEDIUM AAI and app AAI is MEDIUM as well, and the user has 2FA AAI, looks like:
        // access_file_conf.aai_mapping['MEDIUM'] = ['2FA'];
        // app.aai = 'MEDIUM;
        // user.aai = ['2FA'];
        // Thus user should be allowed for this app (it requires MEDIUM, and MEDIUM requires 2FA, and user has 2FA
        // indeed)

        var aai_pass = false;
        if (access_file_conf.aai_mapping[app.aai].length === 0) {
          // No required (aai_mapping for this app's requested AAI is empty
          aai_pass = true;
        } else {
          for (var y=0; y<user.aai.length; y++) {
            var this_aai = user.aai[y];
            if (access_file_conf.aai_mapping[app.aai].indexOf(this_aai) >= 0) {
              aai_pass = true;
              break;
            }
          }
        }

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

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

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

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

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

  // Define global variables that need some kind of initialization in case they're missing from Auth0
  var groups = user.groups || [];

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

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

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

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

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

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

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

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

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

  // Process the access cache decision
  function access_decision(access_rules, access_file_conf) {
    for (var i=0; i<access_rules.length; i++) {
      var app = access_rules[i].application;

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

      // Defaut app requested aai level to MEDIUM for all apps which do not have this set in access file
      var required_aai_level = "MEDIUM";

      if (app.client_id && (app.client_id.indexOf(context.clientID) >= 0)) {
        // Set app AAI level if present
        required_aai_level = app.aai || required_aai_level;

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

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

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

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

    // AAI (AUTHENTICATOR ASSURANCE INDICATOR) REQUIREMENTS
    //
    // Note that user.aai is set in another rule (rules/aai.js)
    //
    // Mapping logic and verification
    // Ex: our mapping says 2FA for MEDIUM AAI and app AAI is MEDIUM as well, and the user has 2FA AAI, looks like:
    // access_file_conf.aai_mapping['MEDIUM'] = ['2FA'];
    // app.aai = 'MEDIUM;
    // user.aai = ['2FA'];
    // Thus user should be allowed for this app (it requires MEDIUM, and MEDIUM requires 2FA, and user has 2FA
    // indeed)

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

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

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

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


<commit_before><commit_msg>create digitalReadAll, use it in trackLine, remove digitalRead<commit_after><|endoftext|>


<commit_before><commit_msg>FLARE<commit_after><|endoftext|>