From 8927d56b5cf16e35973d57c69548d58cbd6ecbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Wed, 12 Dec 2018 09:40:37 +0100 Subject: [PATCH 01/24] do not create new error when error is catched --- scm-ui/src/config/modules/config.js | 14 ++---- scm-ui/src/groups/modules/groups.js | 25 +++++------ scm-ui/src/repos/modules/repos.js | 5 +-- .../repos/permissions/modules/permissions.js | 44 +++++++------------ scm-ui/src/repos/sources/modules/sources.js | 3 +- scm-ui/src/users/modules/users.js | 27 +++--------- 6 files changed, 39 insertions(+), 79 deletions(-) diff --git a/scm-ui/src/config/modules/config.js b/scm-ui/src/config/modules/config.js index 352afefb70..2d14fcfea6 100644 --- a/scm-ui/src/config/modules/config.js +++ b/scm-ui/src/config/modules/config.js @@ -32,9 +32,8 @@ export function fetchConfig(link: string) { .then(data => { dispatch(fetchConfigSuccess(data)); }) - .catch(cause => { - const error = new Error(`could not fetch config: ${cause.message}`); - dispatch(fetchConfigFailure(error)); + .catch(err => { + dispatch(fetchConfigFailure(err)); }); }; } @@ -73,13 +72,8 @@ export function modifyConfig(config: Config, callback?: () => void) { callback(); } }) - .catch(cause => { - dispatch( - modifyConfigFailure( - config, - new Error(`could not modify config: ${cause.message}`) - ) - ); + .catch(err => { + dispatch(modifyConfigFailure(config, err)); }); }; } diff --git a/scm-ui/src/groups/modules/groups.js b/scm-ui/src/groups/modules/groups.js index 165648edaa..483b5b3798 100644 --- a/scm-ui/src/groups/modules/groups.js +++ b/scm-ui/src/groups/modules/groups.js @@ -54,9 +54,8 @@ export function fetchGroupsByLink(link: string) { .then(data => { dispatch(fetchGroupsSuccess(data)); }) - .catch(cause => { - const error = new Error(`could not fetch groups: ${cause.message}`); - dispatch(fetchGroupsFailure(link, error)); + .catch(err => { + dispatch(fetchGroupsFailure(link, err)); }); }; } @@ -105,9 +104,8 @@ function fetchGroup(link: string, name: string) { .then(data => { dispatch(fetchGroupSuccess(data)); }) - .catch(cause => { - const error = new Error(`could not fetch group: ${cause.message}`); - dispatch(fetchGroupFailure(name, error)); + .catch(err => { + dispatch(fetchGroupFailure(name, err)); }); }; } @@ -151,10 +149,10 @@ export function createGroup(link: string, group: Group, callback?: () => void) { callback(); } }) - .catch(error => { + .catch(err => { dispatch( createGroupFailure( - new Error(`Failed to create group ${group.name}: ${error.message}`) + err ) ); }); @@ -201,11 +199,11 @@ export function modifyGroup(group: Group, callback?: () => void) { .then(() => { dispatch(fetchGroupByLink(group)); }) - .catch(cause => { + .catch(err => { dispatch( modifyGroupFailure( group, - new Error(`could not modify group ${group.name}: ${cause.message}`) + err ) ); }); @@ -259,11 +257,8 @@ export function deleteGroup(group: Group, callback?: () => void) { callback(); } }) - .catch(cause => { - const error = new Error( - `could not delete group ${group.name}: ${cause.message}` - ); - dispatch(deleteGroupFailure(group, error)); + .catch(err => { + dispatch(deleteGroupFailure(group, err)); }); }; } diff --git a/scm-ui/src/repos/modules/repos.js b/scm-ui/src/repos/modules/repos.js index 642f6cf395..3e574aa938 100644 --- a/scm-ui/src/repos/modules/repos.js +++ b/scm-ui/src/repos/modules/repos.js @@ -224,9 +224,8 @@ export function modifyRepo(repository: Repository, callback?: () => void) { .then(() => { dispatch(fetchRepoByLink(repository)); }) - .catch(cause => { - const error = new Error(`failed to modify repo: ${cause.message}`); - dispatch(modifyRepoFailure(repository, error)); + .catch(err => { + dispatch(modifyRepoFailure(repository, err)); }); }; } diff --git a/scm-ui/src/repos/permissions/modules/permissions.js b/scm-ui/src/repos/permissions/modules/permissions.js index 154ee8123f..9f5330bbcd 100644 --- a/scm-ui/src/repos/permissions/modules/permissions.js +++ b/scm-ui/src/repos/permissions/modules/permissions.js @@ -1,12 +1,16 @@ // @flow -import type {Action} from "@scm-manager/ui-components"; -import {apiClient} from "@scm-manager/ui-components"; +import type { Action } from "@scm-manager/ui-components"; +import { apiClient } from "@scm-manager/ui-components"; import * as types from "../../../modules/types"; -import type {Permission, PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types"; -import {isPending} from "../../../modules/pending"; -import {getFailure} from "../../../modules/failure"; -import {Dispatch} from "redux"; +import type { + Permission, + PermissionCollection, + PermissionCreateEntry +} from "@scm-manager/ui-types"; +import { isPending } from "../../../modules/pending"; +import { getFailure } from "../../../modules/failure"; +import { Dispatch } from "redux"; export const FETCH_PERMISSIONS = "scm/permissions/FETCH_PERMISSIONS"; export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${ @@ -141,13 +145,8 @@ export function modifyPermission( callback(); } }) - .catch(cause => { - const error = new Error( - `failed to modify permission: ${cause.message}` - ); - dispatch( - modifyPermissionFailure(permission, error, namespace, repoName) - ); + .catch(err => { + dispatch(modifyPermissionFailure(permission, err, namespace, repoName)); }); }; } @@ -241,15 +240,7 @@ export function createPermission( } }) .catch(err => - dispatch( - createPermissionFailure( - new Error( - `failed to add permission ${permission.name}: ${err.message}` - ), - namespace, - repoName - ) - ) + dispatch(createPermissionFailure(err, namespace, repoName)) ); }; } @@ -318,13 +309,8 @@ export function deletePermission( callback(); } }) - .catch(cause => { - const error = new Error( - `could not delete permission ${permission.name}: ${cause.message}` - ); - dispatch( - deletePermissionFailure(permission, namespace, repoName, error) - ); + .catch(err => { + dispatch(deletePermissionFailure(permission, namespace, repoName, err)); }); }; } diff --git a/scm-ui/src/repos/sources/modules/sources.js b/scm-ui/src/repos/sources/modules/sources.js index 5868c56df3..c6d86d38ee 100644 --- a/scm-ui/src/repos/sources/modules/sources.js +++ b/scm-ui/src/repos/sources/modules/sources.js @@ -25,8 +25,7 @@ export function fetchSources( dispatch(fetchSourcesSuccess(repository, revision, path, sources)); }) .catch(err => { - const error = new Error(`failed to fetch sources: ${err.message}`); - dispatch(fetchSourcesFailure(repository, revision, path, error)); + dispatch(fetchSourcesFailure(repository, revision, path, err)); }); }; } diff --git a/scm-ui/src/users/modules/users.js b/scm-ui/src/users/modules/users.js index fe751d13d4..ab330d9ffd 100644 --- a/scm-ui/src/users/modules/users.js +++ b/scm-ui/src/users/modules/users.js @@ -35,8 +35,6 @@ export const DELETE_USER_FAILURE = `${DELETE_USER}_${types.FAILURE_SUFFIX}`; const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2"; -// TODO i18n for error messages - // fetch users export function fetchUsers(link: string) { @@ -57,9 +55,8 @@ export function fetchUsersByLink(link: string) { .then(data => { dispatch(fetchUsersSuccess(data)); }) - .catch(cause => { - const error = new Error(`could not fetch users: ${cause.message}`); - dispatch(fetchUsersFailure(link, error)); + .catch(err => { + dispatch(fetchUsersFailure(link, err)); }); }; } @@ -108,9 +105,8 @@ function fetchUser(link: string, name: string) { .then(data => { dispatch(fetchUserSuccess(data)); }) - .catch(cause => { - const error = new Error(`could not fetch user: ${cause.message}`); - dispatch(fetchUserFailure(name, error)); + .catch(err => { + dispatch(fetchUserFailure(name, err)); }); }; } @@ -155,13 +151,7 @@ export function createUser(link: string, user: User, callback?: () => void) { callback(); } }) - .catch(err => - dispatch( - createUserFailure( - new Error(`failed to add user ${user.name}: ${err.message}`) - ) - ) - ); + .catch(err => dispatch(createUserFailure(err))); }; } @@ -260,11 +250,8 @@ export function deleteUser(user: User, callback?: () => void) { callback(); } }) - .catch(cause => { - const error = new Error( - `could not delete user ${user.name}: ${cause.message}` - ); - dispatch(deleteUserFailure(user, error)); + .catch(err => { + dispatch(deleteUserFailure(user, err)); }); }; } From c3a8ef99861a17c6ff7e73d2176fd5387966b308 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Wed, 12 Dec 2018 13:29:37 +0100 Subject: [PATCH 02/24] implemented unauthorized error message and link to reload page --- .../ui-components/src/ErrorNotification.js | 19 ++++++++++++++----- scm-ui/public/locales/en/commons.json | 3 ++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/ErrorNotification.js b/scm-ui-components/packages/ui-components/src/ErrorNotification.js index 5600d81799..8225c0914a 100644 --- a/scm-ui-components/packages/ui-components/src/ErrorNotification.js +++ b/scm-ui-components/packages/ui-components/src/ErrorNotification.js @@ -2,6 +2,7 @@ import React from "react"; import { translate } from "react-i18next"; import Notification from "./Notification"; +import { UNAUTHORIZED_ERROR } from "./apiclient"; type Props = { t: string => string, @@ -12,11 +13,19 @@ class ErrorNotification extends React.Component { render() { const { t, error } = this.props; if (error) { - return ( - - {t("error-notification.prefix")}: {error.message} - - ); + if (error == UNAUTHORIZED_ERROR) { + return ( + + {t("error-notification.prefix")}: {t("error-notification.timeout")} Login + + ); + } else { + return ( + + {t("error-notification.prefix")}: {error.message} + + ); + } } return null; } diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index 47a8735e5b..fe1062e789 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -20,7 +20,8 @@ } }, "error-notification": { - "prefix": "Error" + "prefix": "Error", + "timeout": "The session has expired. Please login again." }, "loading": { "alt": "Loading ..." From b27e3d26007ba0c547e33beee121aa46e6319807 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Wed, 12 Dec 2018 13:35:58 +0100 Subject: [PATCH 03/24] added translation --- .../ui-components/src/ErrorNotification.js | 23 +++++++++++++++---- scm-ui/public/locales/en/commons.json | 4 +++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/ErrorNotification.js b/scm-ui-components/packages/ui-components/src/ErrorNotification.js index 5600d81799..7856f29789 100644 --- a/scm-ui-components/packages/ui-components/src/ErrorNotification.js +++ b/scm-ui-components/packages/ui-components/src/ErrorNotification.js @@ -2,6 +2,7 @@ import React from "react"; import { translate } from "react-i18next"; import Notification from "./Notification"; +import {UNAUTHORIZED_ERROR} from "./apiclient"; type Props = { t: string => string, @@ -9,14 +10,26 @@ type Props = { }; class ErrorNotification extends React.Component { + render() { const { t, error } = this.props; if (error) { - return ( - - {t("error-notification.prefix")}: {error.message} - - ); + if (error === UNAUTHORIZED_ERROR) { + return ( + + {t("error-notification.prefix")}: {t("error-notification.timeout")} + {" "} + {t("error-notification.loginLink")} + + ); + } else { + return ( + + {t("error-notification.prefix")}: {error.message} + + ); + } + } return null; } diff --git a/scm-ui/public/locales/en/commons.json b/scm-ui/public/locales/en/commons.json index 47a8735e5b..2908a38a4f 100644 --- a/scm-ui/public/locales/en/commons.json +++ b/scm-ui/public/locales/en/commons.json @@ -20,7 +20,9 @@ } }, "error-notification": { - "prefix": "Error" + "prefix": "Error", + "loginLink": "You can login here again.", + "timeout": "The session has expired." }, "loading": { "alt": "Loading ..." From 9b1de3cfb4a73fd176daa7ae794d11ff9809e33f Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Wed, 12 Dec 2018 17:14:21 +0100 Subject: [PATCH 04/24] started impementation of wordbreak for table --- scm-ui/src/repos/sources/components/FileTreeLeaf.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index b4e2ad59ea..36bd734d11 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -10,6 +10,13 @@ import type { File } from "@scm-manager/ui-types"; const styles = { iconColumn: { width: "16px" + }, + wordBreakTable: { + WebkitHyphens: "auto", + MozHyphens: "auto", + MsHyphens: "auto", + hyphens: "auto", + wordBreak: "auto" } }; @@ -71,7 +78,7 @@ class FileTreeLeaf extends React.Component { return ( {this.createFileIcon(file)} - {this.createFileName(file)} + {this.createFileName(file)} {fileSize} From d6efec8fe3f67bd8932ff1e5c0f92ada19bcf5b9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 14 Dec 2018 08:28:28 +0100 Subject: [PATCH 05/24] use url safe base64 encoding --- .../main/java/sonia/scm/security/DefaultCipherHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java b/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java index b4f0d81cd3..9c1fa590cc 100644 --- a/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java +++ b/scm-core/src/main/java/sonia/scm/security/DefaultCipherHandler.java @@ -164,7 +164,7 @@ public class DefaultCipherHandler implements CipherHandler { String result = null; try { - byte[] encodedInput = Base64.getDecoder().decode(value); + byte[] encodedInput = Base64.getUrlDecoder().decode(value); byte[] salt = new byte[SALT_LENGTH]; byte[] encoded = new byte[encodedInput.length - SALT_LENGTH]; @@ -221,7 +221,7 @@ public class DefaultCipherHandler implements CipherHandler { System.arraycopy(salt, 0, result, 0, SALT_LENGTH); System.arraycopy(encodedInput, 0, result, SALT_LENGTH, result.length - SALT_LENGTH); - res = new String(Base64.getEncoder().encode(result), ENCODING); + res = new String(Base64.getUrlEncoder().encode(result), ENCODING); } catch (IOException | GeneralSecurityException ex) { throw new CipherException("could not encode string", ex); } From 306482094d4181bf8ede9d9e8048e858a9042026 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 14 Dec 2018 08:29:30 +0100 Subject: [PATCH 06/24] move AccessTokenCookieIssue from scm-webapp to scm-core --- .../scm/security/AccessTokenCookieIssuer.java | 30 +++++++++++++++++++ .../main/java/sonia/scm/ScmServletModule.java | 5 ++-- ...va => DefaultAccessTokenCookieIssuer.java} | 8 ++--- ...> DefaultAccessTokenCookieIssuerTest.java} | 6 ++-- 4 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/security/AccessTokenCookieIssuer.java rename scm-webapp/src/main/java/sonia/scm/security/{AccessTokenCookieIssuer.java => DefaultAccessTokenCookieIssuer.java} (93%) rename scm-webapp/src/test/java/sonia/scm/security/{AccessTokenCookieIssuerTest.java => DefaultAccessTokenCookieIssuerTest.java} (93%) diff --git a/scm-core/src/main/java/sonia/scm/security/AccessTokenCookieIssuer.java b/scm-core/src/main/java/sonia/scm/security/AccessTokenCookieIssuer.java new file mode 100644 index 0000000000..999c693b8f --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/AccessTokenCookieIssuer.java @@ -0,0 +1,30 @@ +package sonia.scm.security; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Generates cookies and invalidates access token cookies. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public interface AccessTokenCookieIssuer { + + /** + * Creates a cookie for token authentication and attaches it to the response. + * + * @param request http servlet request + * @param response http servlet response + * @param accessToken access token + */ + void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken); + /** + * Invalidates the authentication cookie. + * + * @param request http servlet request + * @param response http servlet response + */ + void invalidate(HttpServletRequest request, HttpServletResponse response); + +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 9555ad66b5..d7846dbac5 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -79,14 +79,14 @@ import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.repository.xml.XmlRepositoryDAO; import sonia.scm.schedule.QuartzScheduler; import sonia.scm.schedule.Scheduler; +import sonia.scm.security.AccessTokenCookieIssuer; import sonia.scm.security.AuthorizationChangedEventProducer; import sonia.scm.security.CipherHandler; import sonia.scm.security.CipherUtil; import sonia.scm.security.ConfigurableLoginAttemptHandler; -import sonia.scm.security.DefaultJwtAccessTokenRefreshStrategy; +import sonia.scm.security.DefaultAccessTokenCookieIssuer; import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.DefaultSecuritySystem; -import sonia.scm.security.JwtAccessTokenRefreshStrategy; import sonia.scm.security.KeyGenerator; import sonia.scm.security.LoginAttemptHandler; import sonia.scm.security.SecuritySystem; @@ -320,6 +320,7 @@ public class ScmServletModule extends ServletModule // bind events // bind(LastModifiedUpdateListener.class); + bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class); bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class); } diff --git a/scm-webapp/src/main/java/sonia/scm/security/AccessTokenCookieIssuer.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAccessTokenCookieIssuer.java similarity index 93% rename from scm-webapp/src/main/java/sonia/scm/security/AccessTokenCookieIssuer.java rename to scm-webapp/src/main/java/sonia/scm/security/DefaultAccessTokenCookieIssuer.java index bb1473dca6..fd3f0e0d6f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AccessTokenCookieIssuer.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAccessTokenCookieIssuer.java @@ -51,12 +51,12 @@ import java.util.concurrent.TimeUnit; * @author Sebastian Sdorra * @since 2.0.0 */ -public final class AccessTokenCookieIssuer { +public final class DefaultAccessTokenCookieIssuer implements AccessTokenCookieIssuer { /** - * the logger for AccessTokenCookieIssuer + * the logger for DefaultAccessTokenCookieIssuer */ - private static final Logger LOG = LoggerFactory.getLogger(AccessTokenCookieIssuer.class); + private static final Logger LOG = LoggerFactory.getLogger(DefaultAccessTokenCookieIssuer.class); private final ScmConfiguration configuration; @@ -66,7 +66,7 @@ public final class AccessTokenCookieIssuer { * @param configuration scm main configuration */ @Inject - public AccessTokenCookieIssuer(ScmConfiguration configuration) { + public DefaultAccessTokenCookieIssuer(ScmConfiguration configuration) { this.configuration = configuration; } diff --git a/scm-webapp/src/test/java/sonia/scm/security/AccessTokenCookieIssuerTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAccessTokenCookieIssuerTest.java similarity index 93% rename from scm-webapp/src/test/java/sonia/scm/security/AccessTokenCookieIssuerTest.java rename to scm-webapp/src/test/java/sonia/scm/security/DefaultAccessTokenCookieIssuerTest.java index 03cf174226..9c80cfc67b 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/AccessTokenCookieIssuerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAccessTokenCookieIssuerTest.java @@ -20,11 +20,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) -public class AccessTokenCookieIssuerTest { +public class DefaultAccessTokenCookieIssuerTest { private ScmConfiguration configuration; - private AccessTokenCookieIssuer issuer; + private DefaultAccessTokenCookieIssuer issuer; @Mock private HttpServletRequest request; @@ -41,7 +41,7 @@ public class AccessTokenCookieIssuerTest { @Before public void setUp() { configuration = new ScmConfiguration(); - issuer = new AccessTokenCookieIssuer(configuration); + issuer = new DefaultAccessTokenCookieIssuer(configuration); } @Test From 6f29aed9df95c1a27f4a5d3a6941d2438cbacc9f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 14 Dec 2018 08:33:45 +0100 Subject: [PATCH 07/24] fixed broken AuthenticationResourceTest --- .../sonia/scm/api/v2/resources/AuthenticationResourceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java index 42428f9f77..1123dc94ce 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java @@ -18,6 +18,7 @@ import sonia.scm.security.AccessToken; import sonia.scm.security.AccessTokenBuilder; import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.AccessTokenCookieIssuer; +import sonia.scm.security.DefaultAccessTokenCookieIssuer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -46,7 +47,7 @@ public class AuthenticationResourceTest { @Mock private AccessTokenBuilder accessTokenBuilder; - private AccessTokenCookieIssuer cookieIssuer = new AccessTokenCookieIssuer(mock(ScmConfiguration.class)); + private AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class)); private static final String AUTH_JSON_TRILLIAN = "{\n" + "\t\"cookie\": true,\n" + From 044a60c7c94a2bfd965ddc50df9bd4046f44d89c Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 14 Dec 2018 10:42:20 +0100 Subject: [PATCH 08/24] implemented word-break for column --- scm-ui/src/repos/sources/components/FileTreeLeaf.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index 36bd734d11..f7f9feb52e 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -11,12 +11,13 @@ const styles = { iconColumn: { width: "16px" }, - wordBreakTable: { + wordBreakColumn: { WebkitHyphens: "auto", MozHyphens: "auto", MsHyphens: "auto", hyphens: "auto", - wordBreak: "auto" + wordBreak: "break-all", + minWidth: "10em" } }; @@ -78,12 +79,12 @@ class FileTreeLeaf extends React.Component { return ( {this.createFileIcon(file)} - {this.createFileName(file)} + {this.createFileName(file)} {fileSize} - {file.description} + {file.description} ); } From 3edd3cbb456b90fd6683a6c81a4cf27d96193401 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 14 Dec 2018 15:03:59 +0100 Subject: [PATCH 09/24] implemented work-break for media and table --- scm-ui/src/repos/sources/containers/Content.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 34d024b2a2..cbd83f9d6f 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -41,6 +41,14 @@ const styles = { isVerticalCenter: { display: "flex", alignItems: "center" + }, + wordBreakColumn: { + WebkitHyphens: "auto", + MozHyphens: "auto", + MsHyphens: "auto", + hypens: "auto", + wordBreak: "break-all", + minWidth: "10em" } }; @@ -93,7 +101,7 @@ class Content extends React.Component { classes.marginInHeader )} /> - {file.name} + {file.name}
{selector}
@@ -125,7 +133,7 @@ class Content extends React.Component { {t("sources.content.path")} - {file.path} + {file.path} {t("sources.content.branch")} @@ -141,7 +149,7 @@ class Content extends React.Component { {t("sources.content.description")} - {description} + {description} From ae28b513584b81ed1d5a7a99dc564a12d06c412a Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 14 Dec 2018 16:28:06 +0100 Subject: [PATCH 10/24] added css-class to branch column and renamed it --- scm-ui/src/repos/sources/containers/Content.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index cbd83f9d6f..33bfc7a8ca 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -42,13 +42,12 @@ const styles = { display: "flex", alignItems: "center" }, - wordBreakColumn: { + wordBreak: { WebkitHyphens: "auto", MozHyphens: "auto", MsHyphens: "auto", hypens: "auto", wordBreak: "break-all", - minWidth: "10em" } }; @@ -101,7 +100,7 @@ class Content extends React.Component { classes.marginInHeader )} /> - {file.name} + {file.name}
{selector}
@@ -129,15 +128,15 @@ class Content extends React.Component { if (!collapsed) { return (
- +
- + - + @@ -149,7 +148,7 @@ class Content extends React.Component { - +
{t("sources.content.path")}{file.path}{file.path}
{t("sources.content.branch")}{revision}{revision}
{t("sources.content.size")}
{t("sources.content.description")}{description}{description}
From 30fdce545530ba6df9bfb99af2f078350ee492aa Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Fri, 14 Dec 2018 16:30:19 +0100 Subject: [PATCH 11/24] corrected wrong approach --- scm-ui/src/repos/sources/containers/Content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 33bfc7a8ca..4565746999 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -128,7 +128,7 @@ class Content extends React.Component { if (!collapsed) { return (
- +
From af7e776fddf742b47ddfe581347ecedf32b849e9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 17 Dec 2018 13:06:11 +0100 Subject: [PATCH 12/24] added getParentKey() to AccessToken interface --- .../main/java/sonia/scm/security/AccessToken.java | 12 ++++++++++++ .../main/java/sonia/scm/security/JwtAccessToken.java | 1 + 2 files changed, 13 insertions(+) diff --git a/scm-core/src/main/java/sonia/scm/security/AccessToken.java b/scm-core/src/main/java/sonia/scm/security/AccessToken.java index c2a5f4b747..ac7700b030 100644 --- a/scm-core/src/main/java/sonia/scm/security/AccessToken.java +++ b/scm-core/src/main/java/sonia/scm/security/AccessToken.java @@ -80,8 +80,20 @@ public interface AccessToken { */ Date getExpiration(); + /** + * Returns refresh expiration of token. + * + * @return refresh expiration + */ Optional getRefreshExpiration(); + /** + * Returns id of the parent key. + * + * @return parent key id + */ + Optional getParentKey(); + /** * Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this * token. For example we could issue a token which can only be used to read a single repository. for more informations diff --git a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java index 8fb5929188..4418cb40a8 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java +++ b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessToken.java @@ -87,6 +87,7 @@ public final class JwtAccessToken implements AccessToken { return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class)); } + @Override public Optional getParentKey() { return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString()); } From f041d63887d0f58fce1a95ee863f100b95c9f142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 17 Dec 2018 14:01:15 +0100 Subject: [PATCH 13/24] Read link from repository for repository config --- .../packages/ui-components/src/config/ConfigurationBinder.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js index 960fe7db21..1b2b37bb19 100644 --- a/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js +++ b/scm-ui-components/packages/ui-components/src/config/ConfigurationBinder.js @@ -63,8 +63,9 @@ class ConfigurationBinder { // route for global configuration, passes the current repository to component - const RepoRoute = ({ url, repository }) => { - return this.route(url + to, ); + const RepoRoute = ({url, repository}) => { + const link = repository._links[linkName].href + return this.route(url + to, ); }; // bind config route to extension point From 3daf4f76747153c8888f8eee8cc727b44756653f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 17 Dec 2018 14:58:51 +0100 Subject: [PATCH 14/24] implemented in-memory version of DataStore for testing --- .../sonia/scm/store/InMemoryDataStore.java | 53 +++++++++++++++++++ .../scm/store/InMemoryDataStoreFactory.java | 26 +++++++++ 2 files changed, 79 insertions(+) create mode 100644 scm-test/src/main/java/sonia/scm/store/InMemoryDataStore.java create mode 100644 scm-test/src/main/java/sonia/scm/store/InMemoryDataStoreFactory.java diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryDataStore.java b/scm-test/src/main/java/sonia/scm/store/InMemoryDataStore.java new file mode 100644 index 0000000000..06198d89bf --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryDataStore.java @@ -0,0 +1,53 @@ +package sonia.scm.store; + +import sonia.scm.security.KeyGenerator; +import sonia.scm.security.UUIDKeyGenerator; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * In memory store implementation of {@link DataStore}. + * + * @author Sebastian Sdorra + * + * @param type of stored object + */ +public class InMemoryDataStore implements DataStore { + + private final Map store = new HashMap<>(); + private KeyGenerator generator = new UUIDKeyGenerator(); + + @Override + public String put(T item) { + String key = generator.createKey(); + store.put(key, item); + return key; + } + + @Override + public void put(String id, T item) { + store.put(id, item); + } + + @Override + public Map getAll() { + return Collections.unmodifiableMap(store); + } + + @Override + public void clear() { + store.clear(); + } + + @Override + public void remove(String id) { + store.remove(id); + } + + @Override + public T get(String id) { + return store.get(id); + } +} diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryDataStoreFactory.java b/scm-test/src/main/java/sonia/scm/store/InMemoryDataStoreFactory.java new file mode 100644 index 0000000000..b0e95e9f9c --- /dev/null +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryDataStoreFactory.java @@ -0,0 +1,26 @@ +package sonia.scm.store; + +/** + * In memory configuration store factory for testing purposes. + * + * @author Sebastian Sdorra + */ +public class InMemoryDataStoreFactory implements DataStoreFactory { + + private InMemoryDataStore store; + + public InMemoryDataStoreFactory() { + } + + public InMemoryDataStoreFactory(InMemoryDataStore store) { + this.store = store; + } + + @Override + public DataStore getStore(TypedStoreParameters storeParameters) { + if (store != null) { + return store; + } + return new InMemoryDataStore<>(); + } +} From 480801d3b00953d1f9e3006e0a23ebbc6b2de0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 17 Dec 2018 15:23:25 +0100 Subject: [PATCH 15/24] Do not keep directory for stores --- .../scm/store/FileBasedStoreFactory.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java index 099ab53baa..d37a150723 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java @@ -58,8 +58,6 @@ public abstract class FileBasedStoreFactory { private RepositoryLocationResolver repositoryLocationResolver; private Store store; - private File storeDirectory; - protected FileBasedStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, Store store) { this.contextProvider = contextProvider; this.repositoryLocationResolver = repositoryLocationResolver; @@ -75,17 +73,16 @@ public abstract class FileBasedStoreFactory { } protected File getStoreLocation(String name, Class type, Repository repository) { - if (storeDirectory == null) { - if (repository != null) { - LOG.debug("create store with type: {}, name: {} and repository: {}", type, name, repository.getNamespaceAndName()); - storeDirectory = this.getStoreDirectory(store, repository); - } else { - LOG.debug("create store with type: {} and name: {} ", type, name); - storeDirectory = this.getStoreDirectory(store); - } - IOUtil.mkdirs(storeDirectory); + File storeDirectory; + if (repository != null) { + LOG.debug("create store with type: {}, name: {} and repository: {}", type, name, repository.getNamespaceAndName()); + storeDirectory = this.getStoreDirectory(store, repository); + } else { + LOG.debug("create store with type: {} and name: {} ", type, name); + storeDirectory = this.getStoreDirectory(store); } - return new File(this.storeDirectory, name); + IOUtil.mkdirs(storeDirectory); + return new File(storeDirectory, name); } /** From 4c0f99329326cb3711d115624a524fc7c53aefb7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 18 Dec 2018 09:26:51 +0100 Subject: [PATCH 16/24] added jaxb adapter for instant objects --- .../java/sonia/scm/xml/XmlInstantAdapter.java | 25 ++++++++++ .../sonia/scm/xml/XmlInstantAdapterTest.java | 47 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java create mode 100644 scm-core/src/test/java/sonia/scm/xml/XmlInstantAdapterTest.java diff --git a/scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java b/scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java new file mode 100644 index 0000000000..9b8d718851 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java @@ -0,0 +1,25 @@ +package sonia.scm.xml; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +/** + * JAXB adapter for {@link Instant} objects. + * + * @since 2.0.0 + */ +public class XmlInstantAdapter extends XmlAdapter { + + @Override + public String marshal(Instant instant) { + return DateTimeFormatter.ISO_INSTANT.format(instant); + } + + @Override + public Instant unmarshal(String text) { + TemporalAccessor parsed = DateTimeFormatter.ISO_INSTANT.parse(text); + return Instant.from(parsed); + } +} diff --git a/scm-core/src/test/java/sonia/scm/xml/XmlInstantAdapterTest.java b/scm-core/src/test/java/sonia/scm/xml/XmlInstantAdapterTest.java new file mode 100644 index 0000000000..eb1ea86aee --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/xml/XmlInstantAdapterTest.java @@ -0,0 +1,47 @@ +package sonia.scm.xml; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; + +import javax.xml.bind.JAXB; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.nio.file.Path; +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(TempDirectory.class) +class XmlInstantAdapterTest { + + @Test + void shouldMarshalAndUnmarshalInstant(@TempDirectory.TempDir Path tempDirectory) { + Path path = tempDirectory.resolve("instant.xml"); + + Instant instant = Instant.now(); + InstantObject object = new InstantObject(instant); + JAXB.marshal(object, path.toFile()); + + InstantObject unmarshaled = JAXB.unmarshal(path.toFile(), InstantObject.class); + assertEquals(instant, unmarshaled.instant); + } + + @XmlRootElement(name = "instant-object") + @XmlAccessorType(XmlAccessType.FIELD) + public static class InstantObject { + + @XmlJavaTypeAdapter(XmlInstantAdapter.class) + private Instant instant; + + public InstantObject() { + } + + InstantObject(Instant instant) { + this.instant = instant; + } + } + +} From 13e3d54100b2d8aa1bba3b843355a84073cfbbb1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 18 Dec 2018 14:55:38 +0100 Subject: [PATCH 17/24] added constructor with predefined store --- .../scm/store/InMemoryConfigurationStoreFactory.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java index 2c5641bfd1..2180afdca2 100644 --- a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java @@ -42,8 +42,20 @@ package sonia.scm.store; */ public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory { + private ConfigurationStore store; + + public InMemoryConfigurationStoreFactory() { + } + + public InMemoryConfigurationStoreFactory(ConfigurationStore store) { + this.store = store; + } + @Override public ConfigurationStore getStore(TypedStoreParameters storeParameters) { + if (store != null) { + return store; + } return new InMemoryConfigurationStore<>(); } } From 3bb85463885bda502f5426e1ea747ca7e796983e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Tue, 18 Dec 2018 14:57:00 +0000 Subject: [PATCH 18/24] Close branch feature/changes-for-cas-plugin From 30c9b87cd3d7be614bd8188fc0d5734a0ead09e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Wed, 19 Dec 2018 08:40:04 +0100 Subject: [PATCH 19/24] added is-clipped class, and show description not at mobile view --- scm-ui/src/repos/containers/RepositoryRoot.js | 2 +- scm-ui/src/repos/sources/components/FileTree.js | 4 +++- scm-ui/src/repos/sources/components/FileTreeLeaf.js | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scm-ui/src/repos/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index 81de0296fc..a3f69fe70b 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -114,7 +114,7 @@ class RepositoryRoot extends React.Component { return (
-
+
{
- + diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index f7f9feb52e..724a80e742 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -6,6 +6,7 @@ import FileSize from "./FileSize"; import FileIcon from "./FileIcon"; import { Link } from "react-router-dom"; import type { File } from "@scm-manager/ui-types"; +import classNames from "classnames"; const styles = { iconColumn: { @@ -84,7 +85,9 @@ class FileTreeLeaf extends React.Component { - + ); } From ee90707fcc82d792a0780e08adb1075697b6f286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Wed, 19 Dec 2018 07:50:48 +0000 Subject: [PATCH 20/24] Close branch bug/single_source_overflow From 0d8e0fef8e6fdf7d2afb7555729f52b1482ee151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maren=20S=C3=BCwer?= Date: Wed, 19 Dec 2018 08:26:50 +0000 Subject: [PATCH 21/24] Close branch bug/table_navi_overflow From d8c5349994bde1ed7cfde64debaf3806a33189b4 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Wed, 19 Dec 2018 09:47:44 +0100 Subject: [PATCH 22/24] outsourced and refactored is-word-break class --- scm-ui/src/repos/sources/components/FileTreeLeaf.js | 11 +++-------- scm-ui/styles/scm.scss | 8 ++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index 724a80e742..20905a8354 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -12,12 +12,7 @@ const styles = { iconColumn: { width: "16px" }, - wordBreakColumn: { - WebkitHyphens: "auto", - MozHyphens: "auto", - MsHyphens: "auto", - hyphens: "auto", - wordBreak: "break-all", + wordBreakMinWidth: { minWidth: "10em" } }; @@ -80,12 +75,12 @@ class FileTreeLeaf extends React.Component { return ( - + - diff --git a/scm-ui/styles/scm.scss b/scm-ui/styles/scm.scss index 4d306df6ad..8937ae3068 100644 --- a/scm-ui/styles/scm.scss +++ b/scm-ui/styles/scm.scss @@ -27,6 +27,14 @@ $blue: #33B2E8; padding: 0 0 0 3.8em !important; } +.is-word-break { + -webkit-hyphens: auto; + -moz-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto; + word-break: break-all; +} + .main { min-height: calc(100vh - 260px); } From d93b83d969004affc0b14873173bda3f1bb5d89f Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Wed, 19 Dec 2018 11:07:39 +0100 Subject: [PATCH 23/24] added wordBreak class in scm.scss --- scm-ui/src/repos/sources/containers/Content.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 4565746999..2c5663da0c 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -41,13 +41,6 @@ const styles = { isVerticalCenter: { display: "flex", alignItems: "center" - }, - wordBreak: { - WebkitHyphens: "auto", - MozHyphens: "auto", - MsHyphens: "auto", - hypens: "auto", - wordBreak: "break-all", } }; @@ -100,7 +93,7 @@ class Content extends React.Component { classes.marginInHeader )} /> - {file.name} + {file.name}
{selector}
@@ -132,11 +125,11 @@ class Content extends React.Component {
- + - + @@ -148,7 +141,7 @@ class Content extends React.Component { - +
{t("sources.content.path")} {t("sources.file-tree.lastModified")} {t("sources.file-tree.description")} + {t("sources.file-tree.description")} +
{file.description} + {file.description} +
{this.createFileIcon(file)}{this.createFileName(file)}{this.createFileName(file)} {fileSize} + {file.description}
{t("sources.content.path")}{file.path}{file.path}
{t("sources.content.branch")}{revision}{revision}
{t("sources.content.size")}
{t("sources.content.description")}{description}{description}
From 25f2867d23647e8685473e88e3458ac00a2a1367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Wed, 19 Dec 2018 10:37:45 +0000 Subject: [PATCH 24/24] Close branch feature/session_timeout