From 0123f1c3f9dafd48d1cd5d0eb85a8eafe7879f83 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Fri, 1 Dec 2023 15:55:22 +0100 Subject: [PATCH] Finished #396 - Fixed the tests - Updated frontend state - Fixed the access token refresh --- .../dbrepo-realm.json | 56 +++++++++---------- .../java/at/tuwien/mapper/TableMapper.java | 26 ++------- .../service/ViewServiceIntegrationTest.java | 5 +- .../tuwien/service/impl/TableServiceImpl.java | 2 +- .../main/java/at/tuwien/test/BaseTest.java | 4 +- dbrepo-ui/api/authentication.service.js | 12 +--- dbrepo-ui/layouts/default.vue | 17 ------ .../_database_id/table/_table_id/data.vue | 2 +- dbrepo-ui/plugins/axios.js | 20 ++++--- dbrepo-ui/plugins/vuex-persist.js | 17 +++++- dbrepo-ui/store/index.js | 3 +- 11 files changed, 72 insertions(+), 92 deletions(-) diff --git a/dbrepo-authentication-service/dbrepo-realm.json b/dbrepo-authentication-service/dbrepo-realm.json index 235c16a720..dbd6c6cc5b 100644 --- a/dbrepo-authentication-service/dbrepo-realm.json +++ b/dbrepo-authentication-service/dbrepo-realm.json @@ -5,10 +5,10 @@ "defaultSignatureAlgorithm" : "RS256", "revokeRefreshToken" : false, "refreshTokenMaxReuse" : 1, - "accessTokenLifespan" : 720, + "accessTokenLifespan" : 86400, "accessTokenLifespanForImplicitFlow" : 900, - "ssoSessionIdleTimeout" : 1296000, - "ssoSessionMaxLifespan" : 1296000, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, "ssoSessionIdleTimeoutRememberMe" : 0, "ssoSessionMaxLifespanRememberMe" : 0, "offlineSessionIdleTimeout" : 2592000, @@ -2035,7 +2035,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper" ] + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper" ] } }, { "id" : "1849e52a-b8c9-44a8-af3d-ee19376a1ed1", @@ -2061,11 +2061,11 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper" ] } } ], "org.keycloak.userprofile.UserProfileProvider" : [ { - "id" : "5da93330-911b-44c9-b971-5176ed5ce1f9", + "id" : "7970b87f-f28b-4085-8612-43281968f118", "providerId" : "declarative-user-profile", "subComponents" : { }, "config" : { } @@ -2119,7 +2119,7 @@ "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "id" : "88c24c27-94a1-4473-b545-bda821e22216", + "id" : "7b9f9c3b-0636-406b-8ac3-c7259ef423d3", "alias" : "Account verification options", "description" : "Method with which to verity the existing account", "providerId" : "basic-flow", @@ -2141,7 +2141,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "796e78cb-67d5-4411-a163-bcc93afde7d3", + "id" : "70be7410-8823-4c0f-aa70-39d9d0e1c7f2", "alias" : "Authentication Options", "description" : "Authentication options.", "providerId" : "basic-flow", @@ -2170,7 +2170,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "42e0b37c-1cd9-4472-8a3c-aff3f5903283", + "id" : "c3bfd9c1-1e61-4789-9797-7e28867ac15b", "alias" : "Browser - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2192,7 +2192,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "6493953f-c9d1-44e6-8adb-0355eaecb65b", + "id" : "ba6106ab-6fca-43b3-a170-c1db8352fc69", "alias" : "Direct Grant - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2214,7 +2214,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "c247859c-1171-4ac1-b93f-e4f17a758226", + "id" : "bd04da6b-71b0-42d0-a279-4402b7cf35cf", "alias" : "First broker login - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -2236,7 +2236,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "5a61e41a-80fb-4848-a457-90cc1c93b625", + "id" : "2016d479-271f-4dfd-88c3-50635d1d27ea", "alias" : "Handle Existing Account", "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId" : "basic-flow", @@ -2258,7 +2258,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "bf35008a-4f40-44c0-bc26-994cf2b8fc5f", + "id" : "3294bcec-0829-405e-bc34-897eb5ecbf62", "alias" : "Reset - Conditional OTP", "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId" : "basic-flow", @@ -2280,7 +2280,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a001a5ff-780d-4d61-a60d-f0cce53b2726", + "id" : "cdc002be-a391-4853-8c40-8eef726dcfb6", "alias" : "User creation or linking", "description" : "Flow for the existing/non-existing user alternatives", "providerId" : "basic-flow", @@ -2303,7 +2303,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "49f8350a-151f-409d-8a8d-642cfffa2a96", + "id" : "11e9d0a5-743f-44fe-ab9f-59f3e730e20a", "alias" : "Verify Existing Account by Re-authentication", "description" : "Reauthentication of existing account", "providerId" : "basic-flow", @@ -2325,7 +2325,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "ae555215-51f3-426d-ada9-36963bff44ef", + "id" : "c5ea7e5c-c07d-48c5-ba99-4e84ed167bee", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -2361,7 +2361,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "74aae05a-433a-468e-959e-a8e23fcc89c0", + "id" : "e18342ad-98d9-4411-bf59-30e07cf65f98", "alias" : "clients", "description" : "Base authentication for clients", "providerId" : "client-flow", @@ -2397,7 +2397,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "f2aaa4a5-ee8b-42c4-b633-2e7d63bb1fc2", + "id" : "ca97318e-8587-43b4-919b-5c65e2a074f3", "alias" : "direct grant", "description" : "OpenID Connect Resource Owner Grant", "providerId" : "basic-flow", @@ -2426,7 +2426,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "677394eb-81d5-401f-a288-43935b39989a", + "id" : "7f523ba5-6d6b-4a65-9ba0-107b5c3afe7a", "alias" : "docker auth", "description" : "Used by Docker clients to authenticate against the IDP", "providerId" : "basic-flow", @@ -2441,7 +2441,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "8d2809c9-9b3e-4c96-9768-5c89e752676a", + "id" : "c72a0c74-0dc4-45dc-8b62-a77bc42d805c", "alias" : "first broker login", "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId" : "basic-flow", @@ -2464,7 +2464,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "7024c35c-81a5-4f73-ad1e-366f4a488175", + "id" : "5939da5a-b299-49b7-8c07-c45e13d995db", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -2486,7 +2486,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a6764496-699f-42d8-aa04-1e996c5f014e", + "id" : "6a0341db-eec8-4962-bb04-4b8a43dced2a", "alias" : "http challenge", "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId" : "basic-flow", @@ -2508,7 +2508,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "fe150e10-946a-441f-aef5-417e171ca49f", + "id" : "525006c3-c676-4bfb-83cf-79faeafe0183", "alias" : "registration", "description" : "registration flow", "providerId" : "basic-flow", @@ -2524,7 +2524,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "5de2ec52-872f-462c-b994-47f59a31b5ab", + "id" : "65e9d76b-d180-47e6-ad1b-dddea414668f", "alias" : "registration form", "description" : "registration form", "providerId" : "form-flow", @@ -2560,7 +2560,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "5833fbd7-31d4-4cf3-95db-6e6beb40a2a5", + "id" : "feb110ed-e018-43ba-8989-0b17a3a20de3", "alias" : "reset credentials", "description" : "Reset credentials for a user if they forgot their password or something", "providerId" : "basic-flow", @@ -2596,7 +2596,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "64902f62-0b27-4174-8d66-88910be9fcfd", + "id" : "15efa29f-2140-4e3b-803e-64d79906c18b", "alias" : "saml ecp", "description" : "SAML ECP Profile Authentication Flow", "providerId" : "basic-flow", @@ -2612,13 +2612,13 @@ } ] } ], "authenticatorConfig" : [ { - "id" : "e1ad59c1-8db5-4381-b7b9-cc97d5653236", + "id" : "2c135401-6244-41b0-8c49-648f38b5e1bd", "alias" : "create unique user config", "config" : { "require.password.update.after.registration" : "false" } }, { - "id" : "81d8abc7-c14e-4b41-9432-8ea38cd8a0e3", + "id" : "39d00721-0854-4ff7-9f5c-7f6fa71ae37a", "alias" : "review profile config", "config" : { "update.profile.on.first.login" : "missing" diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/TableMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/TableMapper.java index c42c8f1625..d5fe384b34 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/TableMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/TableMapper.java @@ -479,31 +479,17 @@ public interface TableMapper { } default PreparedStatement tableToCreateHistoryViewRawQuery(Connection connection, Table data) throws QueryMalformedException { - final StringBuilder keys = new StringBuilder(); - final StringBuilder statement = new StringBuilder("CREATE VIEW `hs_") + final StringBuilder view = new StringBuilder("CREATE VIEW `hs_") .append(data.getInternalName()) - .append("` AS SELECT * FROM (SELECT "); - final int[] idx = new int[]{0}; - data.getColumns() - .stream() - .filter(c -> Objects.nonNull(c.getIsPrimaryKey())) - .filter(TableColumn::getIsPrimaryKey) - .forEach(c -> keys.append(idx[0]++ > 0 ? "," : "") - .append("`") - .append(c.getInternalName()) - .append("`")); - statement.append(keys) - .append(", ROW_START AS inserted_at, IF(ROW_END > NOW(), NULL, ROW_END) AS deleted_at, COUNT(*) as total FROM `") + .append("` AS SELECT * FROM (SELECT ROW_START AS inserted_at, IF(ROW_END > NOW(), NULL, ROW_END) AS deleted_at, COUNT(*) as total FROM `") .append(data.getInternalName()) - .append("` FOR SYSTEM_TIME ALL GROUP BY ") - .append(keys) - .append(", inserted_at, deleted_at ORDER BY deleted_at DESC LIMIT 50) AS v ORDER BY v.inserted_at, v.deleted_at ASC"); + .append("` FOR SYSTEM_TIME ALL GROUP BY inserted_at, deleted_at ORDER BY deleted_at DESC LIMIT 50) AS v ORDER BY v.inserted_at, v.deleted_at ASC"); try { - final PreparedStatement pstmt = connection.prepareStatement(statement.toString()); - log.trace("prepared create sequence statement {}", statement); + final PreparedStatement pstmt = connection.prepareStatement(view.toString()); + log.trace("prepared create view statement {}", view); return pstmt; } catch (SQLException e) { - log.error("failed to prepare statement {}, reason: {}", statement, e.getMessage()); + log.error("Failed to prepare statement: {}", e.getMessage()); throw new QueryMalformedException("Failed to prepare statement", e); } } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java index caa1271611..68525e6729 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java @@ -135,12 +135,11 @@ public class ViewServiceIntegrationTest extends BaseUnitTest { assertEquals("7.4", resultSet.get(1).get("mintemp")); assertEquals("0", resultSet.get(1).get("rainfall")); assertEquals("Albury", resultSet.get(1).get("location")); - assertEquals("2008-12-01", resultSet.get(1).get("date")); + assertEquals("2008-12-02", resultSet.get(1).get("date")); assertEquals("12.9", resultSet.get(2).get("mintemp")); assertEquals("0", resultSet.get(2).get("rainfall")); assertEquals("Albury", resultSet.get(2).get("location")); - assertEquals("2008-12-01", resultSet.get(2).get("date")); - /* more result checks omitted */ + assertEquals("2008-12-03", resultSet.get(2).get("date")); } @Test diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index ae0a2643a2..16bfa35731 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -180,7 +180,7 @@ public class TableServiceImpl extends HibernateConnector implements TableService final Connection connection = dataSource.getConnection(); generatedSequence = tableMapper.tableToCreateTableRawQuery(connection, createDto); } catch (Exception e) { - log.error("Failed to create table, reason: {}", e.getMessage()); + log.error("Failed to create table: {}", e.getMessage()); throw new TableMalformedException("Failed to create table", e); } finally { dataSource.close(); diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java index 50547102ad..91fc524226 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java @@ -5291,8 +5291,8 @@ public abstract class BaseTest { public final static Long VIEW_3_CONTAINER_ID = CONTAINER_1_ID; public final static Long VIEW_3_DATABASE_ID = DATABASE_1_ID; public final static Boolean VIEW_3_PUBLIC = false; - public final static String VIEW_3_QUERY = "select w.`mintemp`, w.`rainfall`, w.`location`, m.`date` from `weather_aus` w join `junit2` m on m.`location` = w.`location`"; - public final static String VIEW_3_QUERY_HASH = "297bbacf5bf142028d0f4a1e537db03fd91b0c3be9e66ea2abc13d2984d22824"; + public final static String VIEW_3_QUERY = "select w.`mintemp`, w.`rainfall`, w.`location`, m.`date` from `weather_aus` w join `junit2` m on m.`location` = w.`location` and m.`date` = w.`date`"; + public final static String VIEW_3_QUERY_HASH = "bbbaa56a5206b3dc3e6cf9301b0db9344eb6f19b100c7b88550ffb597a0bd255"; public final static List<TableColumn> VIEW_3_COLUMNS = List.of(TableColumn.builder() .id(COLUMN_1_4_ID) diff --git a/dbrepo-ui/api/authentication.service.js b/dbrepo-ui/api/authentication.service.js index 6f5d92d411..035a810620 100644 --- a/dbrepo-ui/api/authentication.service.js +++ b/dbrepo-ui/api/authentication.service.js @@ -91,19 +91,9 @@ class AuthenticationService { }).catch((error) => { console.error('Failed to authenticate', error) const { response } = error - const { status, data } = response + const { status } = response if (status === 401) { Vue.$toast.error('Invalid username-password combination.') - } else if (data && data.error && data.error === 'invalid_grant') { - store().commit('SET_TOKEN', null) - store().commit('SET_REFRESH_TOKEN', null) - store().commit('SET_ROLES', []) - store().commit('SET_USER', null) - this.$vuetify.theme.dark = false - Vue.$toast.warning('Authentication expired.') - this.$router.push('/login') - } else { - /* ignore */ } reject(error) }) diff --git a/dbrepo-ui/layouts/default.vue b/dbrepo-ui/layouts/default.vue index 946b88a2de..66d6ee2385 100644 --- a/dbrepo-ui/layouts/default.vue +++ b/dbrepo-ui/layouts/default.vue @@ -149,7 +149,6 @@ </template> <script> -import AuthenticationService from '@/api/authentication.service' import DatabaseService from '@/api/database.service' import TableService from '@/api/table.service' @@ -172,12 +171,6 @@ export default { availableLocales () { return this.$i18n.locales.filter(i => i.code !== this.$i18n.locale) }, - token () { - return this.$store.state.token - }, - refreshToken () { - return this.$store.state.refreshToken - }, user () { return this.$store.state.user }, @@ -221,13 +214,6 @@ export default { this.$store.commit('SET_LOCALE', this.$i18n.locale) } }, - $route: { - handler () { - if (this.refreshToken) { - AuthenticationService.authenticateToken(this.refreshToken) - } - } - }, '$route.params.database_id': { handler (id, oldId) { if (id !== oldId) { @@ -314,9 +300,6 @@ export default { if (!this.$route.params.database_id) { return } - if (!this.token) { - return - } this.loading = true DatabaseService.checkAccess(this.$route.params.database_id) .then((access) => { diff --git a/dbrepo-ui/pages/database/_database_id/table/_table_id/data.vue b/dbrepo-ui/pages/database/_database_id/table/_table_id/data.vue index 8a3df803ef..dde1904cff 100644 --- a/dbrepo-ui/pages/database/_database_id/table/_table_id/data.vue +++ b/dbrepo-ui/pages/database/_database_id/table/_table_id/data.vue @@ -8,7 +8,7 @@ </v-toolbar-title> <v-spacer /> <v-toolbar-title> - <v-btn class="mr-2" :loading="downloadLoading" @click.stop="download"> + <v-btn :loading="downloadLoading" @click.stop="download"> <v-icon left>mdi-download</v-icon> Download csv </v-btn> <v-btn @click="pick"> diff --git a/dbrepo-ui/plugins/axios.js b/dbrepo-ui/plugins/axios.js index 0f67762dbf..7d475a2934 100644 --- a/dbrepo-ui/plugins/axios.js +++ b/dbrepo-ui/plugins/axios.js @@ -11,20 +11,26 @@ api.interceptors.request.use((config) => { return config } const { exp } = jwtDecode(token) - if (new Date(exp) <= new Date()) { + let accessTokenExpiryDate = new Date(exp * 1000) + if (accessTokenExpiryDate <= Date.now()) { /* token expired */ + console.warn('access token has expired:', accessTokenExpiryDate) const refreshToken = store().state.refreshToken - const { exp2 } = jwtDecode(refreshToken) - if (new Date(exp2) <= new Date()) { + const refreshTokenExpiryDate = new Date(jwtDecode(refreshToken).exp * 1000) + if (refreshTokenExpiryDate <= Date.now()) { /* refresh token expired */ + console.error('Refresh token expired') store().commit('SET_TOKEN', null) store().commit('SET_REFRESH_TOKEN', null) - console.warn('Refresh token expired') + return config } AuthenticationService.authenticateToken(refreshToken) - .then((authentication) => { - // console.debug('interceptor inject authorization header for url', config.url) - config.headers.Authorization = `Bearer ${authentication.access_token}` + .then((response) => { + accessTokenExpiryDate = new Date(jwtDecode(response.access_token).exp * 1000) + console.info('Successfully requested a new access token') + console.debug('new access token expires:', accessTokenExpiryDate) + console.debug('attach access token to intercepted request:', config.url) + config.headers.Authorization = `Bearer ${response.access_token}` return config }) .finally(() => { diff --git a/dbrepo-ui/plugins/vuex-persist.js b/dbrepo-ui/plugins/vuex-persist.js index e74b481794..d9f892c78c 100644 --- a/dbrepo-ui/plugins/vuex-persist.js +++ b/dbrepo-ui/plugins/vuex-persist.js @@ -4,9 +4,24 @@ export default ({ store }) => { new VuexPersistence({ storage: window.localStorage, reducer: state => ({ + title: state.title, + icon: state.icon, token: state.token, refreshToken: state.refreshToken, - user: state.user + roles: state.roles, + user: state.user, + database: state.database, + table: state.table, + access: state.access, + locale: state.locale, + messages: state.messages, + ontologies: state.ontologies, + clientId: state.clientId, + clientSecret: state.clientSecret, + searchUsername: state.searchUsername, + searchPassword: state.searchPassword, + databaseCount: state.databaseCount, + doiUrl: state.doiUrl }) }).plugin(store) } diff --git a/dbrepo-ui/store/index.js b/dbrepo-ui/store/index.js index b919a18df9..db1da92da9 100644 --- a/dbrepo-ui/store/index.js +++ b/dbrepo-ui/store/index.js @@ -10,6 +10,7 @@ Vue.use(Vuex) // https://github.com/hua1995116/webchat/blob/7c6544d3defd41cb7cf68306accea97800858bc3/client/src/store/index.js#L293 const store = new Store({ + // changes to the state information here *NEED* to be manually propagated to @/plugins/vuex-persist.js to be stored in the web-browser state: { title: null, icon: null, @@ -158,7 +159,7 @@ const store = new Store({ logout ({ state, commit }) { commit('SET_TOKEN', null) commit('SET_REFRESH_TOKEN', null) - commit('SET_ROLES', null) + commit('SET_ROLES', []) commit('SET_USER', null) commit('SET_DATABASE', null) commit('SET_ACCESS', null) -- GitLab