From 30f8267698cfe90fae65f8aaa08bd5c9f8370791 Mon Sep 17 00:00:00 2001 From: Gavin Rehkemper Date: Thu, 12 Oct 2023 14:54:04 -0500 Subject: [PATCH] Fix: Token with sprite and glyphs (#192) * export Util.formatStyle so we can run unit tests * add unit test to show token issue * fix sprite token issue * unit test to check if token added to glyphs * fix token added to glyphs (Fonts) * Update spec/UtilSpec.js Co-authored-by: Patrick Arlt <378557+patrickarlt@users.noreply.github.com> * Update spec/UtilSpec.js Co-authored-by: Patrick Arlt <378557+patrickarlt@users.noreply.github.com> * Update spec/UtilSpec.js Co-authored-by: Patrick Arlt <378557+patrickarlt@users.noreply.github.com> * Update spec/UtilSpec.js Co-authored-by: Patrick Arlt <378557+patrickarlt@users.noreply.github.com> * check for same domain between style URL and sprite/glyph URL * updated test names --------- Co-authored-by: Patrick Arlt <378557+patrickarlt@users.noreply.github.com> --- spec/UtilSpec.js | 149 +++++++++++++++++++++++++++++++++++++++ src/EsriLeafletVector.js | 1 + src/Util.js | 29 ++++++-- 3 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 spec/UtilSpec.js diff --git a/spec/UtilSpec.js b/spec/UtilSpec.js new file mode 100644 index 0000000..116292a --- /dev/null +++ b/spec/UtilSpec.js @@ -0,0 +1,149 @@ +/* eslint-env mocha */ +const metadata = { + tiles: ['tile/{z}/{y}/{x}.pbf'], + tileInfo: { + lods: [ + { + level: 0, + resolution: 78271.516964, + scale: 295828763.7957775 + } + ] + } +}; + +describe('Util', function () { + it('should include the token in the sprite URL when the sprite URL is relative', function () { + const spriteUrl = '../sprites/sprite'; + const token = 'asdf'; + const styleUrl = + 'https://tiles.arcgis.com/tiles/test/arcgis/rest/services/test/VectorTileServer/resources/styles/root.json'; + const fullSpriteUrl = + 'https://tiles.arcgis.com/tiles/test/arcgis/rest/services/test/VectorTileServer/resources/sprites/sprite'; + + const style = L.esri.Vector.Util.formatStyle( + { + version: 8, + sprite: spriteUrl, + glyphs: '../fonts/{fontstack}/{range}.pbf', + sources: { + esri: { + type: 'vector', + attribution: 'test', + bounds: [-180, -85.0511, 180, 85.0511], + minzoom: 0, + maxzoom: 19, + scheme: 'xyz', + url: '../../' + } + }, + layers: [] + }, + styleUrl, + metadata, + token + ); + + // console.log("style.sprite", style.sprite); + expect(style.sprite).to.equal(`${fullSpriteUrl}?token=${token}`); + }); + + it('should include the token in the sprite URL when the sprite URL starts with https', function () { + const spriteUrl = + 'https://www.arcgis.com/sharing/rest/content/items/123456789/resources/sprites/sprite-1679474043120'; + const styleUrl = + 'https://www.arcgis.com/sharing/rest/content/items/asdf/resources/styles/root.json'; + const token = 'asdf'; + + const style = L.esri.Vector.Util.formatStyle( + { + version: 8, + sprite: spriteUrl, + glyphs: '../fonts/{fontstack}/{range}.pbf', + sources: { + esri: { + type: 'vector', + attribution: 'test', + bounds: [-180, -85.0511, 180, 85.0511], + minzoom: 0, + maxzoom: 19, + scheme: 'xyz', + url: '../../' + } + }, + layers: [] + }, + styleUrl, + metadata, + token + ); + + expect(style.sprite).to.equal(`${spriteUrl}?token=${token}`); + }); + + it('should include the token in the glyph URL when the glyph URL is relative', function () { + const token = 'asdf'; + const glyphUrl = '../fonts/{fontstack}/{range}.pbf'; + const styleUrl = + 'https://tiles.arcgis.com/tiles/test/arcgis/rest/services/test/VectorTileServer/resources/styles/root.json'; + const fullGlyphUrl = + 'https://tiles.arcgis.com/tiles/test/arcgis/rest/services/test/VectorTileServer/resources/fonts/{fontstack}/{range}.pbf'; + + const style = L.esri.Vector.Util.formatStyle( + { + version: 8, + sprite: 'https://www.arcgis.com/sharing/rest/content/items/123456789/resources/sprites/sprite-1679474043120', + glyphs: glyphUrl, + sources: { + esri: { + type: 'vector', + attribution: 'test', + bounds: [-180, -85.0511, 180, 85.0511], + minzoom: 0, + maxzoom: 19, + scheme: 'xyz', + url: '../../' + } + }, + layers: [] + }, + styleUrl, + metadata, + token + ); + + expect(style.glyphs).to.equal(`${fullGlyphUrl}?token=${token}`); + }); + + it('should include the token in the glyph URL when the glyph URL starts with https', function () { + const token = 'asdf'; + const glyphUrl = 'https://www.arcgis.com/sharing/rest/content/items/123456789//resources/fonts/{fontstack}/{range}.pbf'; + const styleUrl = + 'https://www.arcgis.com/sharing/rest/content/items/asdf/resources/styles/root.json'; + + const style = L.esri.Vector.Util.formatStyle( + { + version: 8, + sprite: 'https://www.arcgis.com/sharing/rest/content/items/123456789/resources/sprites/sprite-1679474043120', + glyphs: glyphUrl, + sources: { + esri: { + type: 'vector', + attribution: 'test', + bounds: [-180, -85.0511, 180, 85.0511], + minzoom: 0, + maxzoom: 19, + scheme: 'xyz', + url: '../../' + } + }, + layers: [] + }, + styleUrl, + metadata, + token + ); + + expect(style.glyphs).to.equal(`${glyphUrl}?token=${token}`); + }); +}); diff --git a/src/EsriLeafletVector.js b/src/EsriLeafletVector.js index 0252c46..bdabf58 100644 --- a/src/EsriLeafletVector.js +++ b/src/EsriLeafletVector.js @@ -5,4 +5,5 @@ export { version as VERSION }; export { VectorBasemapLayer, vectorBasemapLayer } from './VectorBasemapLayer'; export { VectorTileLayer, vectorTileLayer } from './VectorTileLayer'; +export { EsriUtil as Util } from './Util'; export { MaplibreGLJSLayer, maplibreGLJSLayer } from './MaplibreGLLayer'; diff --git a/src/Util.js b/src/Util.js index 73818d8..c8e3631 100644 --- a/src/Util.js +++ b/src/Util.js @@ -142,6 +142,10 @@ function loadStyleFromUrl (styleUrl, options, callback) { request(styleUrl, params, callback); } +function isSameTLD (url1, url2) { + return (new URL(url1)).hostname === (new URL(url2)).hostname; +} + export function formatStyle (style, styleUrl, metadata, token) { // transforms style object in place and also returns it @@ -210,9 +214,14 @@ export function formatStyle (style, styleUrl, metadata, token) { 'styles/root.json', style.sprite.replace('../', '') ); - - // add the token to the style.sprite property as a query param - style.sprite += token ? '?token=' + token : ''; + } + if (style.sprite && token) { + // add the token to the style.sprite property as a query param, only if same domain (for token security) + if (isSameTLD(styleUrl, style.sprite)) { + style.sprite += '?token=' + token; + } else { + console.warn('Passing a token but sprite URL is not on same base URL, so you must pass the token manually.'); + } } if (style.glyphs && style.glyphs.indexOf('http') === -1) { @@ -221,9 +230,15 @@ export function formatStyle (style, styleUrl, metadata, token) { 'styles/root.json', style.glyphs.replace('../', '') ); + } + if (style.glyphs && token) { // add the token to the style.glyphs property as a query param - style.glyphs += token ? '?token=' + token : ''; + if (isSameTLD(styleUrl, style.glyphs)) { + style.glyphs += '?token=' + token; + } else { + console.warn('Passing a token but glyph URL is not on same base URL, so you must pass the token manually.'); + } } return style; @@ -277,3 +292,9 @@ const WEB_MERCATOR_WKIDS = [3857, 102100, 102113]; export function isWebMercator (wkid) { return WEB_MERCATOR_WKIDS.indexOf(wkid) >= 0; } + +export var EsriUtil = { + formatStyle: formatStyle +}; + +export default EsriUtil;