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-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-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); } 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; + } + } + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java index d6820bad06..630236b20b 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractGitCommandTestBase.java @@ -35,8 +35,10 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import org.junit.After; +import org.junit.Before; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.GitRepositoryConfig; +import sonia.scm.store.InMemoryConfigurationStore; import sonia.scm.store.InMemoryConfigurationStoreFactory; /** @@ -69,7 +71,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase { if (context == null) { - context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())); + context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create())); } return context; 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 99ef942ebc..ee21703c4a 100644 --- a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationStoreFactory.java @@ -35,55 +35,33 @@ package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - /** * In memory configuration store factory for testing purposes. * + * Use {@link #create()} to get a store that creates the same store on each request. + * * @author Sebastian Sdorra */ public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory { - private static final Map STORES = new HashMap<>(); + private ConfigurationStore store; + + public static ConfigurationStoreFactory create() { + return new InMemoryConfigurationStoreFactory(new InMemoryConfigurationStore()); + } + + public InMemoryConfigurationStoreFactory() { + } + + public InMemoryConfigurationStoreFactory(ConfigurationStore store) { + this.store = store; + } @Override public ConfigurationStore getStore(TypedStoreParameters storeParameters) { - Key key = new Key(storeParameters.getType(), storeParameters.getName(), storeParameters.getRepository() == null? "-": storeParameters.getRepository().getId()); - if (STORES.containsKey(key)) { - return STORES.get(key); - } else { - InMemoryConfigurationStore store = new InMemoryConfigurationStore<>(); - STORES.put(key, store); + if (store != null) { return store; } - } - - private static class Key { - private final Class type; - private final String name; - private final String id; - - public Key(Class type, String name, String id) { - this.type = type; - this.name = name; - this.id = id; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Key key = (Key) o; - return Objects.equals(type, key.type) && - Objects.equals(name, key.name) && - Objects.equals(id, key.id); - } - - @Override - public int hashCode() { - return Objects.hash(type, name, id); - } + return new InMemoryConfigurationStore<>(); } } 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<>(); + } +} diff --git a/scm-ui-components/packages/ui-components/src/ErrorNotification.js b/scm-ui-components/packages/ui-components/src/ErrorNotification.js index 5600d81799..6645db5f60 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,25 @@ 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-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 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 ..." 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/containers/RepositoryRoot.js b/scm-ui/src/repos/containers/RepositoryRoot.js index d4095ac065..e73348babc 100644 --- a/scm-ui/src/repos/containers/RepositoryRoot.js +++ b/scm-ui/src/repos/containers/RepositoryRoot.js @@ -101,7 +101,7 @@ class RepositoryRoot extends React.Component { return (
-
+
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/components/FileTree.js b/scm-ui/src/repos/sources/components/FileTree.js index 1dd11870ae..cbe03df62f 100644 --- a/scm-ui/src/repos/sources/components/FileTree.js +++ b/scm-ui/src/repos/sources/components/FileTree.js @@ -119,7 +119,9 @@ class FileTree extends React.Component { {t("sources.file-tree.lastModified")} - {t("sources.file-tree.description")} + + {t("sources.file-tree.description")} + diff --git a/scm-ui/src/repos/sources/components/FileTreeLeaf.js b/scm-ui/src/repos/sources/components/FileTreeLeaf.js index b4e2ad59ea..20905a8354 100644 --- a/scm-ui/src/repos/sources/components/FileTreeLeaf.js +++ b/scm-ui/src/repos/sources/components/FileTreeLeaf.js @@ -6,10 +6,14 @@ 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: { width: "16px" + }, + wordBreakMinWidth: { + minWidth: "10em" } }; @@ -71,12 +75,14 @@ class FileTreeLeaf extends React.Component { return ( {this.createFileIcon(file)} - {this.createFileName(file)} + {this.createFileName(file)} {fileSize} - {file.description} + + {file.description} + ); } diff --git a/scm-ui/src/repos/sources/containers/Content.js b/scm-ui/src/repos/sources/containers/Content.js index 34d024b2a2..2c5663da0c 100644 --- a/scm-ui/src/repos/sources/containers/Content.js +++ b/scm-ui/src/repos/sources/containers/Content.js @@ -93,7 +93,7 @@ class Content extends React.Component { classes.marginInHeader )} /> - {file.name} + {file.name}
{selector}
@@ -125,11 +125,11 @@ class Content extends React.Component { {t("sources.content.path")} - {file.path} + {file.path} {t("sources.content.branch")} - {revision} + {revision} {t("sources.content.size")} @@ -141,7 +141,7 @@ class Content extends React.Component { {t("sources.content.description")} - {description} + {description} 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)); }); }; } 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); } 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/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()); } 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" + 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