From 781605a48939cd76015323d4ff31136457c8e854 Mon Sep 17 00:00:00 2001 From: Quentin Guillemin Date: Thu, 9 Nov 2023 15:46:13 +0100 Subject: [PATCH] feat: use uPortal soffit --- .env.example | 2 + package.json | 2 + .../configuration/InterceptorConfig.java | 47 ++++++++++++++ .../interceptors/SoffitInterceptor.java | 63 +++++++++++++++++++ .../interceptors/beans/SoffitHolder.java | 25 ++++++++ .../collabsoft/web/rest/FileController.java | 16 +++-- src/main/webapp/src/main.ts | 2 + src/main/webapp/src/utils/axiosUtils.ts | 11 ++++ src/main/webapp/src/utils/soffitUtils.ts | 11 ++++ yarn.lock | 18 ++++++ 10 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 src/main/java/fr/recia/collabsoft/configuration/InterceptorConfig.java create mode 100644 src/main/java/fr/recia/collabsoft/interceptors/SoffitInterceptor.java create mode 100644 src/main/java/fr/recia/collabsoft/interceptors/beans/SoffitHolder.java create mode 100644 src/main/webapp/src/utils/soffitUtils.ts diff --git a/.env.example b/.env.example index 4bd90ed5..1739853f 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,6 @@ VITE_BASE_URI="/" VITE_API_URI="/" VITE_REFRESH_IDENTITY_MILLISECONDS=10000 +VITE_USER_INFO_API_URI="" + VITE_PROXY_API_URL="http://localhost:8090" diff --git a/package.json b/package.json index 54475f78..a5c643f7 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@types/lodash.debounce": "^4.0.8", "@types/lodash.isempty": "^4.4.8", "@types/node": "^20.8.10", + "@uportal/open-id-connect": "^1.40.2", "@vitejs/plugin-vue": "^4.4.0", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^12.0.0", @@ -57,6 +58,7 @@ "lodash.isempty": "^4.4.0", "npm-run-all": "^4.1.5", "prettier": "^3.0.3", + "regenerator-runtime": "^0.14.0", "resize-observer-polyfill": "^1.5.1", "sass": "^1.69.5", "typescript": "~5.2.2", diff --git a/src/main/java/fr/recia/collabsoft/configuration/InterceptorConfig.java b/src/main/java/fr/recia/collabsoft/configuration/InterceptorConfig.java new file mode 100644 index 00000000..cc7d11a6 --- /dev/null +++ b/src/main/java/fr/recia/collabsoft/configuration/InterceptorConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 GIP-RECIA, Inc. + * + * 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. + */ +package fr.recia.collabsoft.configuration; + +import fr.recia.collabsoft.interceptors.SoffitInterceptor; +import fr.recia.collabsoft.interceptors.beans.SoffitHolder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class InterceptorConfig implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(soffitInterceptor()); + } + + @Bean + public SoffitInterceptor soffitInterceptor() { + return new SoffitInterceptor(soffitHolder()); + } + + @Bean + @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) + public SoffitHolder soffitHolder() { + return new SoffitHolder(); + } + +} diff --git a/src/main/java/fr/recia/collabsoft/interceptors/SoffitInterceptor.java b/src/main/java/fr/recia/collabsoft/interceptors/SoffitInterceptor.java new file mode 100644 index 00000000..6cac550b --- /dev/null +++ b/src/main/java/fr/recia/collabsoft/interceptors/SoffitInterceptor.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 GIP-RECIA, Inc. + * + * 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. + */ +package fr.recia.collabsoft.interceptors; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.recia.collabsoft.interceptors.beans.SoffitHolder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Base64; +import java.util.Map; + +@Slf4j +public class SoffitInterceptor implements HandlerInterceptor { + + private final SoffitHolder soffitHolder; + + public SoffitInterceptor(SoffitHolder soffitHolder) { + this.soffitHolder = soffitHolder; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = request.getHeader("Authorization"); + if (token == null) { + log.debug("No Authorization header found"); + soffitHolder.setSub(""); + return true; + } + + Base64.Decoder decoder = Base64.getUrlDecoder(); + String payload = new String(decoder.decode(token.replace("Bearer ", "").split("\\.")[1])); + + Map soffit = null; + ObjectMapper objectMapper = new ObjectMapper(); + try { + soffit = objectMapper.readValue(payload, new TypeReference<>() { + }); + soffitHolder.setSub(soffit.get("sub")); + } catch (IOException ignored) { + } + log.debug("Soffit : {}", soffit); + return true; + } + +} diff --git a/src/main/java/fr/recia/collabsoft/interceptors/beans/SoffitHolder.java b/src/main/java/fr/recia/collabsoft/interceptors/beans/SoffitHolder.java new file mode 100644 index 00000000..ef27ac64 --- /dev/null +++ b/src/main/java/fr/recia/collabsoft/interceptors/beans/SoffitHolder.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 GIP-RECIA, Inc. + * + * 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. + */ +package fr.recia.collabsoft.interceptors.beans; + +import lombok.Data; + +@Data +public class SoffitHolder { + + private String sub; + +} diff --git a/src/main/java/fr/recia/collabsoft/web/rest/FileController.java b/src/main/java/fr/recia/collabsoft/web/rest/FileController.java index 257b5a65..6abfc873 100644 --- a/src/main/java/fr/recia/collabsoft/web/rest/FileController.java +++ b/src/main/java/fr/recia/collabsoft/web/rest/FileController.java @@ -34,6 +34,7 @@ import fr.recia.collabsoft.db.repositories.FileRepository; import fr.recia.collabsoft.db.repositories.MetadataRepository; import fr.recia.collabsoft.db.repositories.UserRepository; +import fr.recia.collabsoft.interceptors.beans.SoffitHolder; import fr.recia.collabsoft.pojo.JsonCollaborationBody; import fr.recia.collabsoft.pojo.JsonFileBody; import fr.recia.collabsoft.pojo.JsonHistoryBody; @@ -73,12 +74,15 @@ public class FileController { @Inject private UserRepository userRepository; - // TODO: only for tests - private final Long myId = 1L; + private final SoffitHolder soffitHolder; + + public FileController(SoffitHolder soffitHolder) { + this.soffitHolder = soffitHolder; + } private User getCurrentUser() { return userRepository.findOne( - QUser.user.id.eq(myId) + QUser.user.casUid.eq(soffitHolder.getSub()) ).orElse(null); } @@ -93,7 +97,7 @@ private User getCurrentUser() { @GetMapping public ResponseEntity> getFiles() { final List files = IteratorUtils.toList( - fileRepository.findAll(QFile.file.creator.id.eq(myId)).iterator() + fileRepository.findAll(QFile.file.creator.casUid.eq(soffitHolder.getSub())).iterator() ); if (files.isEmpty()) return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -131,7 +135,7 @@ public ResponseEntity> getSharedFiles() { QFile.file.id.in( JPAExpressions.select(QCollaboration.collaboration.file.id) .from(QCollaboration.collaboration) - .where(QCollaboration.collaboration.user.id.eq(myId)) + .where(QCollaboration.collaboration.user.casUid.eq(soffitHolder.getSub())) ) ).iterator() ); @@ -255,7 +259,7 @@ public ResponseEntity> putMetadata(@PathVariable Long id, @N if (!body.putDataOk()) return new ResponseEntity<>(HttpStatus.BAD_REQUEST); final User user = getCurrentUser(); Metadata metadata = metadataRepository.findOne( - QMetadata.metadata.file.id.eq(id).and(QMetadata.metadata.user.id.eq(myId)) + QMetadata.metadata.file.id.eq(id).and(QMetadata.metadata.user.casUid.eq(soffitHolder.getSub())) ).orElse(null); if (metadata == null) { final File file = fileRepository.findOne( diff --git a/src/main/webapp/src/main.ts b/src/main/webapp/src/main.ts index 72bda646..8f488f01 100644 --- a/src/main/webapp/src/main.ts +++ b/src/main/webapp/src/main.ts @@ -1,3 +1,5 @@ +import 'regenerator-runtime/runtime.js'; + import { createApp } from 'vue'; import { register as registerDirectives } from '@/directives'; diff --git a/src/main/webapp/src/utils/axiosUtils.ts b/src/main/webapp/src/utils/axiosUtils.ts index b7567a7e..08426e1e 100644 --- a/src/main/webapp/src/utils/axiosUtils.ts +++ b/src/main/webapp/src/utils/axiosUtils.ts @@ -1,4 +1,5 @@ import i18n from '@/plugins/i18n'; +import { getToken } from '@/utils/soffitUtils'; import axios from 'axios'; import { useToast } from 'vue-toastification'; @@ -12,6 +13,16 @@ const instance = axios.create({ timeout: 10000, }); +instance.interceptors.request.use(async (config) => { + try { + config.headers['Authorization'] = `Bearer ${(await getToken()).encoded}`; + } catch (e) { + // nothing to do + } + + return config; +}); + const errorHandler = (e: any, toastOrI18n?: boolean | string): void => { const showToast: boolean = typeof toastOrI18n == 'boolean' && toastOrI18n; diff --git a/src/main/webapp/src/utils/soffitUtils.ts b/src/main/webapp/src/utils/soffitUtils.ts new file mode 100644 index 00000000..3d8107a5 --- /dev/null +++ b/src/main/webapp/src/utils/soffitUtils.ts @@ -0,0 +1,11 @@ +import oidc, { type JWT } from '@uportal/open-id-connect'; + +const { VITE_USER_INFO_API_URI } = import.meta.env; + +const getToken = async (): Promise<{ encoded: string; decoded: JWT }> => { + const { encoded, decoded } = await oidc({ userInfoApiUrl: VITE_USER_INFO_API_URI }); + + return { encoded, decoded }; +}; + +export { getToken }; diff --git a/yarn.lock b/yarn.lock index 79a47ae8..4404f217 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1206,6 +1206,15 @@ __metadata: languageName: node linkType: hard +"@uportal/open-id-connect@npm:^1.40.2": + version: 1.40.2 + resolution: "@uportal/open-id-connect@npm:1.40.2" + dependencies: + jwt-decode: "npm:^3.0.0" + checksum: 0e16ad2b0725e0eb1340ba30b0f2d3b7580e7a494364bfc4fab736431616738073d9125a8c6b83544c6f49974928756e2130d04932dc1e78fc81ac629c334519 + languageName: node + linkType: hard + "@vitejs/plugin-vue@npm:^4.4.0": version: 4.4.0 resolution: "@vitejs/plugin-vue@npm:4.4.0" @@ -2075,6 +2084,7 @@ __metadata: "@types/lodash.debounce": "npm:^4.0.8" "@types/lodash.isempty": "npm:^4.4.8" "@types/node": "npm:^20.8.10" + "@uportal/open-id-connect": "npm:^1.40.2" "@vitejs/plugin-vue": "npm:^4.4.0" "@vue/eslint-config-prettier": "npm:^8.0.0" "@vue/eslint-config-typescript": "npm:^12.0.0" @@ -2095,6 +2105,7 @@ __metadata: npm-run-all: "npm:^4.1.5" pinia: "npm:^2.1.7" prettier: "npm:^3.0.3" + regenerator-runtime: "npm:^0.14.0" resize-observer-polyfill: "npm:^1.5.1" sass: "npm:^1.69.5" typescript: "npm:~5.2.2" @@ -4193,6 +4204,13 @@ __metadata: languageName: node linkType: hard +"jwt-decode@npm:^3.0.0": + version: 3.1.2 + resolution: "jwt-decode@npm:3.1.2" + checksum: 20a4b072d44ce3479f42d0d2c8d3dabeb353081ba4982e40b83a779f2459a70be26441be6c160bfc8c3c6eadf9f6380a036fbb06ac5406b5674e35d8c4205eeb + languageName: node + linkType: hard + "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4"