Skip to content

Commit

Permalink
Fix #10505 Allow to specify use of proxy or cors at layer level (#10526)
Browse files Browse the repository at this point in the history
* fix: remove ui element for force proxy and Allow not secure layers

* fix: ajax logic changed, autoDetectCORS is set to true by default

* new central CORS util file created and used in ajax

* checking CORS before adding in common layer file

* null check on getProxyUrl

* updated individual layer considring to use proxy if needed

* avoid proxy cache to update if response is not okey

* enable user to add http url, show warning instead of error, warning text updated

* test cases updated

* fix: resolve conflicts with url check

* fixed the failed test

* review cesium layers

* include add method in model layer

* improve http check for openlayers wms layer

* fix tests

---------

Co-authored-by: allyoucanmap <stefano.bovio@geosolutionsgroup.com>
  • Loading branch information
rowheat02 and allyoucanmap authored Oct 9, 2024
1 parent c4af813 commit 8b2d33e
Showing 39 changed files with 644 additions and 516 deletions.
67 changes: 67 additions & 0 deletions web/client/api/CORS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2024, GeoSolutions Sas.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

import url from 'url';
import { needProxy } from '../utils/ProxyUtils';

let proxyCache = {};


const getBaseUrl = (uri) => {
const urlParts = url.parse(uri);
return urlParts.protocol + "//" + urlParts.host + urlParts.pathname;
};
/**
* Set the proxy value for cached uri
* @param {string} uri - uri string to test
* @param {boolean} value - value to cache
* @returns the passed value
*/
export const setProxyCacheByUrl = (uri, value)=>{
const baseUrl = getBaseUrl(uri);
proxyCache[baseUrl] = value;
return value;
};
/**
* Get the proxy value for cached uri
* @param {string} uri - uri string to test
* @returns true, false or undefined, if undefined means the value has not been stored
*/
export const getProxyCacheByUrl = (uri)=>{
const baseUrl = getBaseUrl(uri);
return proxyCache[baseUrl];
};
/**
* Perform a fetch request to test if a service support CORS
* @param {string} uri - uri string to test
* @returns true if the proxy is required
*/
export const testCors = (uri) => {
const proxy = getProxyCacheByUrl(uri);
if (needProxy(uri) === false) {
setProxyCacheByUrl(uri, false);
return Promise.resolve(false);
}
if (proxy !== undefined) {
return Promise.resolve(proxy);
}
return fetch(uri, {
method: 'GET',
mode: 'cors'
})
.then((response) => {
if (!response.ok) {
return false;
}
return setProxyCacheByUrl(uri, false);
})
.catch(() => {
// in server side error it goes to response(then) anyway, so we can assume that if we get here we have a cors error with no previewable response
return setProxyCacheByUrl(uri, true);
});
};
8 changes: 0 additions & 8 deletions web/client/components/TOC/fragments/settings/Display.jsx
Original file line number Diff line number Diff line change
@@ -243,14 +243,6 @@ export default class extends React.Component {
onChange={(e) => this.props.onChange("localizedLayerStyles", e.target.checked)}>
<Message msgId="layerProperties.enableLocalizedLayerStyles.label" />&nbsp;<InfoPopover text={<Message msgId="layerProperties.enableLocalizedLayerStyles.tooltip" />} />
</Checkbox>))}
{!this.props.isCesiumActive && (<Checkbox
data-qa="display-forceProxy-option"
value="forceProxy"
key="forceProxy"
onChange={(e) => this.props.onChange("forceProxy", e.target.checked)}
checked={this.props.element.forceProxy} >
<Message msgId="layerProperties.forceProxy"/>
</Checkbox>)}
{(this.props.element?.serverType !== ServerTypes.NO_VENDOR && (
<>
<hr/>
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ describe('test Layer Properties Display module component', () => {
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(14);
expect(inputs.length).toBe(13);
ReactTestUtils.Simulate.focus(inputs[2]);
expect(inputs[2].value).toBe('70');
inputs[8].click();
@@ -105,7 +105,7 @@ describe('test Layer Properties Display module component', () => {
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(13);
expect(inputs.length).toBe(12);
ReactTestUtils.Simulate.focus(inputs[2]);
expect(inputs[2].value).toBe('70');
inputs[8].click();
@@ -199,64 +199,6 @@ describe('test Layer Properties Display module component', () => {
expect(isLocalizedLayerStylesOption).toBeTruthy();
});

it('tests Display component for wms with force proxy option displayed', () => {
const l = {
name: 'layer00',
title: 'Layer',
visibility: true,
storeIndex: 9,
type: 'wms',
url: 'fakeurl',
forceProxy: true
};
const settings = {
options: {opacity: 0.7}
};
ReactDOM.render(<Display element={l} settings={settings}/>, document.getElementById("container"));
const isForceProxyOption = document.querySelector('[data-qa="display-forceProxy-option"]');
expect(isForceProxyOption).toBeTruthy();
});
it('tests Display component for wms with force proxy option in cesium map', () => {
const l = {
name: 'layer00',
title: 'Layer',
visibility: true,
storeIndex: 9,
type: 'wms',
url: 'fakeurl',
forceProxy: true
};
const settings = {
options: {opacity: 0.7}
};
ReactDOM.render(<Display isCesiumActive element={l} settings={settings}/>, document.getElementById("container"));
const isForceProxyOption = document.querySelector('[data-qa="display-forceProxy-option"]');
expect(isForceProxyOption).toBeFalsy();
});
it('tests Display component for wms with force proxy option onChange', () => {
const handlers = {
onChange() {}
};
const spyOn = expect.spyOn(handlers, 'onChange');
const l = {
name: 'layer00',
title: 'Layer',
visibility: true,
storeIndex: 9,
type: 'wms',
url: 'fakeurl',
forceProxy: false
};
const settings = {
options: {opacity: 0.7}
};
ReactDOM.render(<Display element={l} settings={settings} onChange={handlers.onChange}/>, document.getElementById("container"));
const isForceProxyOption = document.querySelector('[data-qa="display-forceProxy-option"]');
expect(isForceProxyOption).toBeTruthy();
ReactTestUtils.Simulate.change(isForceProxyOption, { "target": { "checked": true }});
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[0].arguments).toEqual([ 'forceProxy', true ]);
});

it('tests Layer Properties Legend component for map viewer only', () => {
const l = {
@@ -277,8 +219,8 @@ describe('test Layer Properties Display module component', () => {
expect(comp).toBeTruthy();
const labels = ReactTestUtils.scryRenderedDOMComponentsWithClass( comp, "control-label" );
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
const legendWidth = inputs[12];
const legendHeight = inputs[13];
const legendWidth = inputs[11];
const legendHeight = inputs[12];
// Default legend values
expect(legendWidth.value).toBe('12');
expect(legendHeight.value).toBe('12');
@@ -307,8 +249,8 @@ describe('test Layer Properties Display module component', () => {
expect(comp).toBeTruthy();
const labels = ReactTestUtils.scryRenderedDOMComponentsWithClass( comp, "control-label" );
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
const legendWidth = inputs[11];
const legendHeight = inputs[12];
const legendWidth = inputs[10];
const legendHeight = inputs[11];
// Default legend values
expect(legendWidth.value).toBe('12');
expect(legendHeight.value).toBe('12');
@@ -347,10 +289,10 @@ describe('test Layer Properties Display module component', () => {
const legendPreview = ReactTestUtils.scryRenderedDOMComponentsWithClass( comp, "legend-preview" );
expect(legendPreview).toBeTruthy();
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(14);
let interactiveLegendConfig = inputs[11];
let legendWidth = inputs[12];
let legendHeight = inputs[13];
expect(inputs.length).toBe(13);
let interactiveLegendConfig = inputs[10];
let legendWidth = inputs[11];
let legendHeight = inputs[12];
const img = ReactTestUtils.scryRenderedDOMComponentsWithTag(comp, 'img');

// Check value in img src
@@ -423,8 +365,8 @@ describe('test Layer Properties Display module component', () => {
expect(comp).toBeTruthy();
const inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag( comp, "input" );
expect(inputs).toBeTruthy();
expect(inputs.length).toBe(14);
expect(inputs[12].value).toBe("20");
expect(inputs[13].value).toBe("40");
expect(inputs.length).toBe(13);
expect(inputs[11].value).toBe("20");
expect(inputs[12].value).toBe("40");
});
});
Original file line number Diff line number Diff line change
@@ -39,14 +39,7 @@ export default ({
<Message msgId="catalog.showPreview" />
</Checkbox>
</FormGroup>
{!isNil(service.type) && service.type === "wfs" &&
<FormGroup controlId="allowUnsecureLayers" key="allowUnsecureLayers">
<Checkbox
onChange={(e) => onChangeServiceProperty("allowUnsecureLayers", e.target.checked)}
checked={!isNil(service.allowUnsecureLayers) ? service.allowUnsecureLayers : false}>
<Message msgId="catalog.allowUnsecureLayers.label" />&nbsp;<InfoPopover text={<Message msgId="catalog.allowUnsecureLayers.tooltip" />} />
</Checkbox>
</FormGroup>}

{!isNil(service.type) && service.type === "cog" &&
<FormGroup controlId="fetchMetadata" key="fetchMetadata">
<Checkbox
Original file line number Diff line number Diff line change
@@ -111,13 +111,6 @@ export default ({
<Message msgId="layerProperties.singleTile" />&nbsp;<InfoPopover text={<Message msgId="catalog.singleTile.tooltip" />} />
</Checkbox>
</FormGroup>}
{!isNil(service.type) && service.type === "wms" && <FormGroup controlId="allowUnsecureLayers" key="allowUnsecureLayers">
<Checkbox
onChange={(e) => onChangeServiceProperty("allowUnsecureLayers", e.target.checked)}
checked={!isNil(service.allowUnsecureLayers) ? service.allowUnsecureLayers : false}>
<Message msgId="catalog.allowUnsecureLayers.label" />&nbsp;<InfoPopover text={<Message msgId="catalog.allowUnsecureLayers.tooltip" />} />
</Checkbox>
</FormGroup>}
{(!isNil(service.type) ? (service.type === "csw" && !service.excludeShowTemplate) : false) && (<FormGroup controlId="metadata-template" key="metadata-template" className="metadata-template-editor">
<Checkbox
onChange={() => onToggleTemplate()}
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ describe('Test common advanced settings', () => {
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(3);
expect(fields.length).toBe(2);
});
it('test wms advanced options onChangeServiceProperty autoreload', () => {
const action = {
@@ -52,7 +52,7 @@ describe('Test common advanced settings', () => {
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(3);
expect(fields.length).toBe(2);
const autoload = document.querySelectorAll('input[type="checkbox"]')[0];
const formGroup = document.querySelectorAll('.form-group')[0];
expect(formGroup.textContent.trim()).toBe('catalog.autoload');
@@ -61,30 +61,6 @@ describe('Test common advanced settings', () => {
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[0].arguments).toEqual([ 'autoload', true ]);
});
it('test component onChangeServiceProperty allowUnsecureLayers', () => {
const action = {
onChangeServiceProperty: () => {}
};
const spyOn = expect.spyOn(action, 'onChangeServiceProperty');
ReactDOM.render(<CommonAdvancedSettings
onChangeServiceProperty={action.onChangeServiceProperty}
service={{type: "wfs", allowUnsecureLayers: false}}
/>, document.getElementById("container"));
const advancedSettingsPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingsPanel).toBeTruthy();
const allowUnsecureLayers = document.querySelectorAll('input[type="checkbox"]')[1];
const formGroup = document.querySelectorAll('.form-group')[2];
expect(formGroup.textContent.trim()).toBe('catalog.allowUnsecureLayers.label');
expect(allowUnsecureLayers).toExist();
TestUtils.Simulate.change(allowUnsecureLayers, { "target": { "checked": true }});
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[0].arguments).toEqual([ 'allowUnsecureLayers', true ]);

// Unset allowUnsecureLayers
TestUtils.Simulate.change(allowUnsecureLayers, { "target": { "checked": false }});
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[1].arguments).toEqual([ 'allowUnsecureLayers', false ]);
});
it('test component onChangeServiceProperty fetchMetadata', () => {
const action = {
onChangeServiceProperty: () => {}
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ describe('Test Raster advanced settings', () => {
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(15);
expect(fields.length).toBe(14);
// check disabled refresh button

});
@@ -45,7 +45,7 @@ describe('Test Raster advanced settings', () => {
const advancedSettingPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingPanel).toBeTruthy();
const fields = document.querySelectorAll(".form-group");
expect(fields.length).toBe(13);
expect(fields.length).toBe(12);
const refreshButton = document.querySelectorAll('button')[0];
expect(refreshButton).toBeTruthy();
expect(refreshButton.disabled).toBe(false);
@@ -214,30 +214,6 @@ describe('Test Raster advanced settings', () => {
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[0].arguments).toEqual([ 'layerOptions', { tileSize: 512 } ]);
});
it('test component onChangeServiceProperty allowUnsecureLayers', () => {
const action = {
onChangeServiceProperty: () => {}
};
const spyOn = expect.spyOn(action, 'onChangeServiceProperty');
ReactDOM.render(<RasterAdvancedSettings
onChangeServiceProperty={action.onChangeServiceProperty}
service={{type: "wms", allowUnsecureLayers: false}}
/>, document.getElementById("container"));
const advancedSettingsPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingsPanel).toBeTruthy();
const allowUnsecureLayers = document.querySelectorAll('input[type="checkbox"]')[3];
const formGroup = document.querySelectorAll('.form-group')[4];
expect(formGroup.textContent.trim()).toBe('catalog.allowUnsecureLayers.label');
expect(allowUnsecureLayers).toBeTruthy();
TestUtils.Simulate.change(allowUnsecureLayers, { "target": { "checked": true }});
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[0].arguments).toEqual([ 'allowUnsecureLayers', true ]);

// Unset allowUnsecureLayers
TestUtils.Simulate.change(allowUnsecureLayers, { "target": { "checked": false }});
expect(spyOn).toHaveBeenCalled();
expect(spyOn.calls[1].arguments).toEqual([ 'allowUnsecureLayers', false ]);
});
it('test component onChangeServiceProperty useCacheOption for remote tile grids', () => {
const action = {
onChangeServiceProperty: () => {}
@@ -249,7 +225,7 @@ describe('Test Raster advanced settings', () => {
/>, document.getElementById("container"));
const advancedSettingsPanel = document.getElementsByClassName("mapstore-switch-panel");
expect(advancedSettingsPanel).toBeTruthy();
const formGroup = document.querySelectorAll('.form-group')[7];
const formGroup = document.querySelectorAll('.form-group')[6];
expect(formGroup.textContent.trim()).toBe('layerProperties.useCacheOptionInfo.label');
const useCacheOption = formGroup.querySelector('input[type="checkbox"]');
expect(useCacheOption).toBeTruthy();
21 changes: 14 additions & 7 deletions web/client/components/catalog/editor/MainForm.jsx
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useState, useEffect } from 'react';
import {get, find, isEmpty} from 'lodash';
import React, { useState } from 'react';
import {get, find} from 'lodash';
import Message from '../../I18N/Message';
import HTML from '../../I18N/HTML';

@@ -154,17 +154,21 @@ export default ({
setValid = () => {}
}) => {
const [error, setError] = useState(null);
const [warning, setWarning] = useState(null);
function handleProtocolValidity(url) {
onChangeUrl(url);
if (url) {
const {valid, errorMsgId} = checkUrl(url, null, service?.allowUnsecureLayers);
setError(valid ? null : errorMsgId);
setValid(valid);
if (errorMsgId === "catalog.invalidUrlHttpProtocol") {
setError(null);
setWarning(errorMsgId);
} else {
setWarning(null);
setError(valid ? null : errorMsgId);
setValid(valid);
}
}
}
useEffect(() => {
!isEmpty(service.url) && handleProtocolValidity(service.url);
}, [service?.allowUnsecureLayers]);
const URLEditor = service.type === "tms" ? TmsURLEditor : service.type === "cog" ? COGEditor : DefaultURLEditor;
return (
<Form horizontal >
@@ -195,6 +199,9 @@ export default ({
{error ? <Alert bsStyle="danger">
<Message msgId={error} />
</Alert> : null}
{warning ? <Alert bsStyle="warning">
<Message msgId={warning} />
</Alert> : null}

</Form>);
};
Loading

0 comments on commit 8b2d33e

Please sign in to comment.