a53b9958fa734f73aeffb9ddfe2fbad1ca65f90c galt Mon Jan 30 16:18:41 2017 -0800 Check-in of CSP2 Content-Security-Policy work. All C-language CGIs should now support CSP2 in browser to stop major forms of XSS javascript injection. Javascript on pages is gathered together, and then emitted in a single script block at the end with a nonce that tells the browser, this is js that we generated instead of being injected by a hacker. Both inline script from script blocks and inline js event handlers had to be pulled out and separated. You will not see js sprinkled through-out the page now. Older browsers that support CSP1 or that do not understand CSP at all will still work, just without protection. External js libraries loaded at runtime need to be added to the CSP policy header in src/lib/htmshell.c. diff --git src/hg/js/hgGateway.js src/hg/js/hgGateway.js index 59e18c0..8c3aa9b 100644 --- src/hg/js/hgGateway.js +++ src/hg/js/hgGateway.js @@ -15,30 +15,36 @@ // rainbow: module that exports draw() function and colors. draw() adds stripes using // a spectrum of colors that are associated to species groups. The hgGateway view code // uses coordinates of stripes within the tree image to create a corresponding "rainbow" // slider bar to the left of the phylogenetic tree container. // autocompleteCat: customized JQuery autocomplete plugin that includes watermark and // can display results broken down by category (for example, genomes from various // assembly hubs and native genomes). // hgGateway: module of mostly view/controller code (model state comes directly from server). // Globals (pragma for jshint): /* globals dbDbTree, activeGenomes, surveyLink, surveyLabel, surveyLabelImage, cart */ /* globals calculateHgTracksWidth */ // function is defined in utils.js +window.hgsid = ''; +window.activeGenomes = {}; +window.surveyLink=null; +window.surveyLabel=null; +window.surveyLabelImage=null; + function svgCreateEl(type, config) { // Helper function for creating a new SVG element and initializing its // properties and attributes. Type is something like 'rect', 'text', 'g', etc; // config is an object like { id: 'newThingie', x: 0, y: 10, title: 'blah blah' }. var svgns = 'http://www.w3.org/2000/svg'; var xlinkns = 'http://www.w3.org/1999/xlink'; var el = document.createElementNS(svgns, type); var title, titleEl; if (el) { _.forEach(config, function(value, setting) { if (setting === 'textContent') { // Text content (the text in a text element or title element) is a property: el.textContent = value; } else if (setting === 'href') { @@ -69,32 +75,30 @@ var speciesTree = (function() { // SVG phylogenetic tree of species in the browser (with connected assembly hubs above tree) // Layout parameters/configuration (object passed into draw can override these defaults): var cfg = { labelRightX: 230, labelStartY: 18, speciesLineOffsetX: 8, branchLength: 5, halfTextHeight: 4, hubLineOffset: 100, labelLineHeight: 18, paddingRight: 5, paddingBottom: 5, branchPadding: 2, - onClickSpeciesName: 'console.log', - onClickHubName: 'console.log', trackHubsUrl: '', containerWidth: 370 }; function checkTree(node) { // Return true if node and its descendants are of the form // Array[ label:String, taxId:Number, sciName:String, Array[ [node]...] ] // e.g. ['root', taxId0, sciName0, // [ ['leaf1', taxId1, sciName1, []], // ['leaf2', taxId2, sciName2, []] ] // ] if (! _.isString(node[0])) { console.log('label is not a string', node); return false; } @@ -125,36 +129,40 @@ depth = 0; } else if (kids.length === 1) { // Node with one child: pass-through, depth is child's depth depth = addDepth(kids[0]); } else { // Node with multiple children: depth is 1 + max child depth deepestKid = _.max(kids, addDepth); depth = 1 + deepestKid[4]; } node[4] = depth; return depth; } function addSpeciesLabel(svg, label, taxId, sciName, y) { // Add a species label to svg at y offset - var onClickString = cfg.onClickSpeciesName + '(' + taxId + ')'; var text = svgCreateEl('text', { x: cfg.labelRightX, y: y, + id: 'textEl_' + taxId, name: 'textEl_' + taxId, title: sciName, - textContent: label, - onclick: onClickString }); + textContent: label + }); + // CSP2 will not allow setAttribute with events like onclick, + // no matter whether the value is string or function. + text.onclick = function(){hgGateway.onClickSpeciesLabel(taxId);}; + svg.appendChild(text); } function addLine(svg, x1, y1, x2, y2, mouseover) { // Add optionally with mouseover title var config = { x1: x1, y1: y1, x2: x2, y2: y2, title: mouseover }; var line; if (x1 === x2) { // vertical lines get special styling config.className = 'vert'; } line = svgCreateEl('line', config); svg.appendChild(line); @@ -273,41 +281,37 @@ textContent: 'Hub Genomes', className: 'trackHubsLink' }); a.appendChild(text); svg.appendChild(a); } function doubleQuote(string) { return '"' + string + '"'; } function addHubLabel(svg, hub, y) { // Add a track hub label to svg at y offset var label = hub.shortLabel + ' (' + hub.assemblyCount + ')'; // There are a bunch of hub properties to pass to the onClick handler; // too bad we can't pass a bound function but instead must build a string: - var onClickString = cfg.onClickHubName + '(' + - doubleQuote(hub.hubUrl) + ', ' + - hub.taxId + ', ' + - doubleQuote(hub.defaultDb) + ', ' + - doubleQuote(hub.name) + - ')'; var text = svgCreateEl('text', { x: cfg.labelRightX, y: y, textContent: label, + id: 'textEl_' + hub.name, name: 'textEl_' + hub.name, - onclick: onClickString, - title: hub.longLabel }); + title: hub.longLabel + }); + text.onclick = function() {hgGateway.onClickHubName(hub.hubUrl, hub.taxId, hub.defaultDb, hub.name);}; svg.appendChild(text); } function drawHubs(svg, hubList, yIn) { // Add a label for each hub in hubList, with a separator line below and // "Hub Genomes" link instead of a tree. var y = yIn; var hub, i, textX, textY, lineX1, lineY, lineX2; if (hubList && hubList.length) { for (i = 0; i < hubList.length; i++) { hub = hubList[i]; addHubLabel(svg, hub, y); y += cfg.labelLineHeight; } textX = cfg.labelRightX + cfg.speciesLineOffsetX; @@ -771,31 +775,31 @@ '' + ' FireFox or ' + '' + 'Chrome.' + '

'; // Globals (within this function scope) // Set this to true to see server requests and responses in the console. var debugCartJson = false; // This is a global (within wrapper function scope) so event handlers can use it // without needing to bind functions. var scrollbarWidth = 0; // This holds everything we need to know to draw the page: taxId, db, hubs, description etc. var uiState = {}; // This is used to check whether a taxId is found in activeGenomes: - var activeTaxIds = _.invert(activeGenomes); + var activeTaxIds = []; // gets set in init() now; // This is dbDbTree after pruning -- null if dbDbTree has no children left var prunedDbDbTree = null; // This keeps track of which gene the user has selected most recently from autocomplete. var selectedGene = null; function setupFavIcons() { // Set up onclick handlers for shortcut buttons and labels var haveIcon = false; var i, name, taxId, onClick; for (i = 0; i < favIconTaxId.length; i++) { name = favIconTaxId[i][0]; taxId = favIconTaxId[i][1]; if (activeTaxIds[taxId]) { // When user clicks on icon, set the taxId (default database); // scroll the image to that species and clear the species autocomplete input. @@ -1140,33 +1144,31 @@ } } return hasActiveLeaf; } function drawSpeciesPicker(dbDbTree) { // If dbDbTree is nonempty and SVG is supported, draw the tree; if SVG is not supported, // use the space to suggest that the user install a better browser. // If dbDbTree doesn't exist, leave the "Represented Species" section hidden. var svg, spTree, stripeTops; if (dbDbTree) { if (document.createElementNS) { // Draw the phylogenetic tree and do layout adjustments svg = document.getElementById('speciesTree'); spTree = speciesTree.draw(svg, dbDbTree, uiState.hubs, - { onClickSpeciesName: 'hgGateway.onClickSpeciesLabel', - onClickHubName: 'hgGateway.onClickHubName', - hgHubConnectUrl: 'hgHubConnect?hgsid=' + window.hgsid, + { hgHubConnectUrl: 'hgHubConnect?hgsid=' + window.hgsid, containerWidth: $('#speciesPicker').width() }); setSpeciesPickerSizes(spTree.width, spTree.height); stripeTops = rainbow.draw(svg, dbDbTree, spTree.yTree, spTree.height, spTree.leafTops); } else { $('#speciesTreeContainer').html(getBetterBrowserMessage); } $('#representedSpeciesTitle').show(); $('#speciesGraphic').show(); if (dbDbTree && document.createElementNS) { // These need to be done after things are visible because heights are 0 when hidden. highlightLabelForDb(uiState.db, uiState.taxId); initRainbowSlider(spTree.height, rainbow.colors, stripeTops); } @@ -1636,30 +1638,31 @@ } } function init() { // Boot up the page; initialize elements and install event handlers. var searchObj = {}; // We need a bound function to pass into autocompleteCat.init below; // however, autocompleteFromTree is even slower than drawing the tree because of // all the copying. So bind now, fill in searchObj later. var processSpeciesResults = processSpeciesAutocompleteItems.bind(null, searchObj); cart.setCgi('hgGateway'); cart.debug(debugCartJson); // Get state from cart cart.send({ getUiState: {} }, handleRefreshState); cart.flush(); + activeTaxIds = _.invert(activeGenomes); // Prune inactive genomes from dbDbTree. if (window.dbDbTree) { prunedDbDbTree = dbDbTree; if (! pruneInactive(dbDbTree, activeGenomes, activeTaxIds)) { prunedDbDbTree = null; } } // When page has loaded, do layout adjustments and initialize event handlers. $(function() { scrollbarWidth = findScrollbarWidth(); setRightColumnWidth(); setupFavIcons(); autocompleteCat.init($('#speciesSearch'), { baseUrl: 'hgGateway?hggw_term=', @@ -1675,16 +1678,15 @@ displaySurvey(); replaceHgsidInLinks(); // Fill in searchObj here once everything is displayed. autocompleteFromTree(prunedDbDbTree, searchObj); }); } return { init: init, // For use by speciesTree.draw SVG (text-only onclick): onClickSpeciesLabel: onClickSpeciesLabel, onClickHubName: onClickHubName }; }()); // hgGateway -hgGateway.init();