diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/Bauhaus.iml b/.idea/Bauhaus.iml new file mode 100644 index 000000000..0c8867d7e --- /dev/null +++ b/.idea/Bauhaus.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..f3abf9e99 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..03d9549ea --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..30b9e7f4c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/package.json b/app/package.json index 0ad3c82eb..aa8f0d8eb 100755 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "Bauhaus", - "version": "3.0.18", + "version": "3.1.0", "description": "Web application for the management of concepts, classifications and other statistical objects", "repository": { "type": "git", diff --git a/app/public/OidcServiceWorker.js b/app/public/OidcServiceWorker.js new file mode 100644 index 000000000..956ac8405 --- /dev/null +++ b/app/public/OidcServiceWorker.js @@ -0,0 +1,272 @@ +this.importScripts('OidcTrustedDomains.js'); + +const id = Math.round(new Date().getTime() / 1000).toString(); + +const keepAliveJsonFilename = "OidcKeepAliveServiceWorker.json"; +const handleInstall = (event) => { + console.log('[OidcServiceWorker] service worker installed ' + id); + self.skipWaiting(); +}; + +const handleActivate = () => { + console.log('[OidcServiceWorker] service worker activated ' + id); + self.clients.claim(); +}; + +let currentLoginCallbackConfigurationName = null; +let database = { + default: { + configurationName: "default", + tokens: null, + items:[], + oidcServerConfiguration: null + } +}; + +const countLetter = (str, find)=> { + return (str.split(find)).length - 1; +} + +function extractAccessTokenPayload(accessToken) { + try{ + if (!accessToken) { + return null; + } + if(countLetter(accessToken,'.') === 2) { + return JSON.parse(atob(accessToken.split('.')[1])); + } else { + return null; + } + } catch (e) { + console.warn(e); + } + return null; +} + +function hideTokens(currentDatabaseElement) { + const configurationName = currentDatabaseElement.configurationName; + return (response) => { + return response.json().then(tokens => { + currentDatabaseElement.tokens = tokens; + const secureTokens = { + ...tokens, + access_token: ACCESS_TOKEN +"_" + configurationName, + refresh_token: REFRESH_TOKEN + "_" + configurationName, + }; + const body = JSON.stringify(secureTokens) + return new Response(body, response); + }); + }; +} + +const getCurrentDatabasesTokenEndpoint = (database, url) => { + const databases = []; + for (const [key, value] of Object.entries(database)) { + if(value && value.oidcServerConfiguration !=null && url.startsWith(value.oidcServerConfiguration.tokenEndpoint)){ + databases.push(value); + } + } + return databases; +} + +const getCurrentDatabaseDomain = (database, url) => { + for (const [key, currentDatabase] of Object.entries(database)) { + + const oidcServerConfiguration = currentDatabase.oidcServerConfiguration; + const domainsToSendTokens = oidcServerConfiguration != null ? [ + oidcServerConfiguration.userInfoEndpoint, ...trustedDomains[key] + ] : [...trustedDomains[key]]; + + let hasToSendToken = false; + for(let i=0;i { + let headersObj = {}; + for (let key of headers.keys()) { + headersObj[key] = headers.get(key); + } + return headersObj; +}; + +const REFRESH_TOKEN = 'REFRESH_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER'; +const ACCESS_TOKEN = 'ACCESS_TOKEN_SECURED_BY_OIDC_SERVICE_WORKER'; + +const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +const keepAliveAsync = async (event) => { + const originalRequest = event.request; + const isFromVanilla = originalRequest.headers.has('oidc-vanilla'); + if(!isFromVanilla) { + await sleep(15000); + } + const init = {"status": 200, "statusText": 'oidc-service-worker'}; + return new Response('{}', init); +} + +const handleFetch = async (event) => { + const originalRequest = event.request; + + if(originalRequest.url.includes(keepAliveJsonFilename) ){ + event.respondWith(keepAliveAsync(event)); + return; + } + + const currentDatabaseForRequestAccessToken = getCurrentDatabaseDomain(database, originalRequest.url); + if(currentDatabaseForRequestAccessToken && currentDatabaseForRequestAccessToken.tokens) { + const newRequest = new Request(originalRequest, { + headers: { + ...serializeHeaders(originalRequest.headers), + authorization: "Bearer " + currentDatabaseForRequestAccessToken.tokens.access_token + } + }); + event.waitUntil(event.respondWith(fetch(newRequest))); + } + + if(event.request.method !== "POST"){ + return; + } + let currentDatabase = null; + const currentDatabases = getCurrentDatabasesTokenEndpoint(database, originalRequest.url); + const numberDatabase = currentDatabases.length; + if(numberDatabase > 0) { + const maPromesse = new Promise((resolve, reject) => { + const response = originalRequest.clone().text().then(actualBody => { + if(actualBody.includes(REFRESH_TOKEN)) { + let newBody = actualBody; + for(let i= 0;i { + if(r !== undefined){ + resolve(r); + } + }).catch(err => { + if(err !== undefined) { + reject(err); + } + }); + }); + event.waitUntil(event.respondWith(maPromesse)); + } +}; + +self.addEventListener('install', handleInstall); +self.addEventListener('activate', handleActivate); +self.addEventListener('fetch', handleFetch); + +addEventListener('message', event => { + const port = event.ports[0]; + const data = event.data; + const configurationName = data.configurationName; + let currentDatabase = database[configurationName]; + + if(!currentDatabase){ + database[configurationName] = { + tokens: null, + items:[], + oidcServerConfiguration: null, + configurationName: configurationName, + }; + currentDatabase = database[configurationName]; + if(!trustedDomains[configurationName]) { + trustedDomains[configurationName] = []; + } + } + switch (data.type){ + case "loadItems": + port.postMessage(database[configurationName].items); + return; + case "clear": + currentDatabase.tokens = null; + currentDatabase.items = null; + port.postMessage({configurationName}); + return; + case "init": + currentDatabase.oidcServerConfiguration = data.data.oidcServerConfiguration; + const where = data.data.where; + if(where === "loginCallbackAsync" || where === "tryKeepExistingSessionAsync") { + currentLoginCallbackConfigurationName = configurationName; + } else{ + currentLoginCallbackConfigurationName = null; + } + if(!currentDatabase.tokens){ + port.postMessage({ + tokens:null, + configurationName}); + } else { + port.postMessage({ + tokens: { + ...currentDatabase.tokens, + refresh_token: REFRESH_TOKEN + "_" + configurationName, + access_token: ACCESS_TOKEN + "_" + configurationName + }, + configurationName + }); + } + return; + + case "getAccessTokenPayload": + const accessTokenPayload = extractAccessTokenPayload(currentDatabase.tokens.access_token); + port.postMessage({configurationName, accessTokenPayload}); + return; + default: + currentDatabase.items = data.data; + port.postMessage({configurationName}); + return; + } +}); + diff --git a/app/public/OidcTrustedDomains.js b/app/public/OidcTrustedDomains.js new file mode 100644 index 000000000..af5325fc2 --- /dev/null +++ b/app/public/OidcTrustedDomains.js @@ -0,0 +1,6 @@ + +// Add here trusted domains, access tokens will be send to +const trustedDomains = { + default:["http://localhost:4200"], + auth0:[] +}; \ No newline at end of file diff --git a/app/src/js/applications/classifications/series/visualization/members.js b/app/src/js/applications/classifications/series/visualization/members.js index 8c765d046..2a4be7527 100644 --- a/app/src/js/applications/classifications/series/visualization/members.js +++ b/app/src/js/applications/classifications/series/visualization/members.js @@ -1,6 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import { Panel } from '@inseefr/wilco'; +import { Note } from '@inseefr/wilco'; import { D1, D2 } from 'js/i18n'; export default ({ members, secondLang }) => { @@ -23,17 +23,13 @@ export default ({ members, secondLang }) => { const isMembersLg2 = membersLg2.filter(m => m !== null).length !== 0; return (
-
- -
    {membersLg1}
-
-
+ {membersLg1} + }/> {secondLang && isMembersLg2 && ( -
- -
    {membersLg2}
-
-
+ {membersLg2} + }/> )}
); diff --git a/app/src/js/applications/collections/send/controls.js b/app/src/js/applications/collections/send/controls.js deleted file mode 100644 index e212f0f95..000000000 --- a/app/src/js/applications/collections/send/controls.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { PropTypes } from 'prop-types'; -import { Button, ErrorBloc, ActionToolbar, ReturnButton } from '@inseefr/wilco'; -import D from 'js/i18n'; - -function SendControls({ - isRecipientValid, - subject, - message, - sendMessage, - urlBack, -}) { - const hasSubject = Boolean(subject); - const hasMessage = Boolean(message); - let warning; - let disabled; - if (!isRecipientValid) { - warning = D.invalidMailAdress; - disabled = true; - } else if (!hasSubject) { - warning = D.emptyMailObject; - disabled = true; - } else if (!hasMessage) { - warning = D.emptyMailBody; - } - return ( - <> - - - + + + ) + } + + (fullCodeListValue?.id || fullCodeListValue)?.toString().includes(c.id?.toString()) + )} isOpen={codesFullListPanelOpened} handleBack={() => setFullCodesListPanelOpened(false)}/> + + + (currentCodeList?.id || currentCodeList)?.toString().includes(c.iri?.toString()) + )?.id + }} isOpen={codesPartialListPanelOpened} handleBack={() => setPartialCodesListPanelOpened(false)}/> + + + + ) +} + const DumbComponentDetailEdit = ({ component: initialComponent, concepts, @@ -33,10 +140,8 @@ const DumbComponentDetailEdit = ({ stampListOptions, serverSideError }) => { - const [codesListPanelOpened, setCodesListPanelOpened] = useState(false); const [component, setComponent] = useState(defaultComponent); const { lg1, lg2 } = useContext(AppContext); - useTitle(D.componentTitle, component?.labelLg1 || D.componentsCreateTitle) useEffect(() => { @@ -67,10 +172,7 @@ const DumbComponentDetailEdit = ({ value: id, label, })); - const codeListOptions = codesLists.map(({ id, label }) => ({ - value: id, - label, - })); + const { field, message } = validateComponent(component); return ( @@ -288,34 +390,7 @@ const DumbComponentDetailEdit = ({ )} - {component.range === XSD_CODE_LIST && ( -
-
-