Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-12-20 10:44:44 +01:00
28 changed files with 313 additions and 146 deletions

View File

@@ -80,8 +80,20 @@ public interface AccessToken {
*/ */
Date getExpiration(); Date getExpiration();
/**
* Returns refresh expiration of token.
*
* @return refresh expiration
*/
Optional<Date> getRefreshExpiration(); Optional<Date> getRefreshExpiration();
/**
* Returns id of the parent key.
*
* @return parent key id
*/
Optional<String> getParentKey();
/** /**
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this * 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 * token. For example we could issue a token which can only be used to read a single repository. for more informations

View File

@@ -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);
}

View File

@@ -164,7 +164,7 @@ public class DefaultCipherHandler implements CipherHandler {
String result = null; String result = null;
try { try {
byte[] encodedInput = Base64.getDecoder().decode(value); byte[] encodedInput = Base64.getUrlDecoder().decode(value);
byte[] salt = new byte[SALT_LENGTH]; byte[] salt = new byte[SALT_LENGTH];
byte[] encoded = new byte[encodedInput.length - 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(salt, 0, result, 0, SALT_LENGTH);
System.arraycopy(encodedInput, 0, result, SALT_LENGTH, System.arraycopy(encodedInput, 0, result, SALT_LENGTH,
result.length - 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) { } catch (IOException | GeneralSecurityException ex) {
throw new CipherException("could not encode string", ex); throw new CipherException("could not encode string", ex);
} }

View File

@@ -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<String, Instant> {
@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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -35,8 +35,10 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.junit.After; import org.junit.After;
import org.junit.Before;
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.store.InMemoryConfigurationStore;
import sonia.scm.store.InMemoryConfigurationStoreFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory;
/** /**
@@ -69,7 +71,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
{ {
if (context == null) if (context == null)
{ {
context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())); context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()));
} }
return context; return context;

View File

@@ -35,55 +35,33 @@ package sonia.scm.store;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/** /**
* In memory configuration store factory for testing purposes. * 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 * @author Sebastian Sdorra
*/ */
public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory { public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory {
private static final Map<Key, InMemoryConfigurationStore> 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 @Override
public ConfigurationStore getStore(TypedStoreParameters storeParameters) { public ConfigurationStore getStore(TypedStoreParameters storeParameters) {
Key key = new Key(storeParameters.getType(), storeParameters.getName(), storeParameters.getRepository() == null? "-": storeParameters.getRepository().getId()); if (store != null) {
if (STORES.containsKey(key)) {
return STORES.get(key);
} else {
InMemoryConfigurationStore<Object> store = new InMemoryConfigurationStore<>();
STORES.put(key, store);
return store; return store;
} }
} return new InMemoryConfigurationStore<>();
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);
}
} }
} }

View File

@@ -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 <T> type of stored object
*/
public class InMemoryDataStore<T> implements DataStore<T> {
private final Map<String, T> 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<String, T> 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);
}
}

View File

@@ -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 <T> DataStore<T> getStore(TypedStoreParameters<T> storeParameters) {
if (store != null) {
return store;
}
return new InMemoryDataStore<>();
}
}

View File

@@ -2,6 +2,7 @@
import React from "react"; import React from "react";
import { translate } from "react-i18next"; import { translate } from "react-i18next";
import Notification from "./Notification"; import Notification from "./Notification";
import {UNAUTHORIZED_ERROR} from "./apiclient";
type Props = { type Props = {
t: string => string, t: string => string,
@@ -9,14 +10,25 @@ type Props = {
}; };
class ErrorNotification extends React.Component<Props> { class ErrorNotification extends React.Component<Props> {
render() { render() {
const { t, error } = this.props; const { t, error } = this.props;
if (error) { if (error) {
return ( if (error === UNAUTHORIZED_ERROR) {
<Notification type="danger"> return (
<strong>{t("error-notification.prefix")}:</strong> {error.message} <Notification type="danger">
</Notification> <strong>{t("error-notification.prefix")}:</strong> {t("error-notification.timeout")}
); {" "}
<a href="javascript:window.location.reload(true)">{t("error-notification.loginLink")}</a>
</Notification>
);
} else {
return (
<Notification type="danger">
<strong>{t("error-notification.prefix")}:</strong> {error.message}
</Notification>
);
}
} }
return null; return null;
} }

View File

@@ -63,8 +63,9 @@ class ConfigurationBinder {
// route for global configuration, passes the current repository to component // route for global configuration, passes the current repository to component
const RepoRoute = ({ url, repository }) => { const RepoRoute = ({url, repository}) => {
return this.route(url + to, <RepositoryComponent repository={repository}/>); const link = repository._links[linkName].href
return this.route(url + to, <RepositoryComponent repository={repository} link={link}/>);
}; };
// bind config route to extension point // bind config route to extension point

View File

@@ -20,7 +20,9 @@
} }
}, },
"error-notification": { "error-notification": {
"prefix": "Error" "prefix": "Error",
"loginLink": "You can login here again.",
"timeout": "The session has expired."
}, },
"loading": { "loading": {
"alt": "Loading ..." "alt": "Loading ..."

View File

@@ -32,9 +32,8 @@ export function fetchConfig(link: string) {
.then(data => { .then(data => {
dispatch(fetchConfigSuccess(data)); dispatch(fetchConfigSuccess(data));
}) })
.catch(cause => { .catch(err => {
const error = new Error(`could not fetch config: ${cause.message}`); dispatch(fetchConfigFailure(err));
dispatch(fetchConfigFailure(error));
}); });
}; };
} }
@@ -73,13 +72,8 @@ export function modifyConfig(config: Config, callback?: () => void) {
callback(); callback();
} }
}) })
.catch(cause => { .catch(err => {
dispatch( dispatch(modifyConfigFailure(config, err));
modifyConfigFailure(
config,
new Error(`could not modify config: ${cause.message}`)
)
);
}); });
}; };
} }

View File

@@ -54,9 +54,8 @@ export function fetchGroupsByLink(link: string) {
.then(data => { .then(data => {
dispatch(fetchGroupsSuccess(data)); dispatch(fetchGroupsSuccess(data));
}) })
.catch(cause => { .catch(err => {
const error = new Error(`could not fetch groups: ${cause.message}`); dispatch(fetchGroupsFailure(link, err));
dispatch(fetchGroupsFailure(link, error));
}); });
}; };
} }
@@ -105,9 +104,8 @@ function fetchGroup(link: string, name: string) {
.then(data => { .then(data => {
dispatch(fetchGroupSuccess(data)); dispatch(fetchGroupSuccess(data));
}) })
.catch(cause => { .catch(err => {
const error = new Error(`could not fetch group: ${cause.message}`); dispatch(fetchGroupFailure(name, err));
dispatch(fetchGroupFailure(name, error));
}); });
}; };
} }
@@ -151,10 +149,10 @@ export function createGroup(link: string, group: Group, callback?: () => void) {
callback(); callback();
} }
}) })
.catch(error => { .catch(err => {
dispatch( dispatch(
createGroupFailure( createGroupFailure(
new Error(`Failed to create group ${group.name}: ${error.message}`) err
) )
); );
}); });
@@ -201,11 +199,11 @@ export function modifyGroup(group: Group, callback?: () => void) {
.then(() => { .then(() => {
dispatch(fetchGroupByLink(group)); dispatch(fetchGroupByLink(group));
}) })
.catch(cause => { .catch(err => {
dispatch( dispatch(
modifyGroupFailure( modifyGroupFailure(
group, group,
new Error(`could not modify group ${group.name}: ${cause.message}`) err
) )
); );
}); });
@@ -259,11 +257,8 @@ export function deleteGroup(group: Group, callback?: () => void) {
callback(); callback();
} }
}) })
.catch(cause => { .catch(err => {
const error = new Error( dispatch(deleteGroupFailure(group, err));
`could not delete group ${group.name}: ${cause.message}`
);
dispatch(deleteGroupFailure(group, error));
}); });
}; };
} }

View File

@@ -101,7 +101,7 @@ class RepositoryRoot extends React.Component<Props> {
return ( return (
<Page title={repository.namespace + "/" + repository.name}> <Page title={repository.namespace + "/" + repository.name}>
<div className="columns"> <div className="columns">
<div className="column is-three-quarters"> <div className="column is-three-quarters is-clipped">
<Switch> <Switch>
<Route <Route
path={url} path={url}

View File

@@ -224,9 +224,8 @@ export function modifyRepo(repository: Repository, callback?: () => void) {
.then(() => { .then(() => {
dispatch(fetchRepoByLink(repository)); dispatch(fetchRepoByLink(repository));
}) })
.catch(cause => { .catch(err => {
const error = new Error(`failed to modify repo: ${cause.message}`); dispatch(modifyRepoFailure(repository, err));
dispatch(modifyRepoFailure(repository, error));
}); });
}; };
} }

View File

@@ -1,12 +1,16 @@
// @flow // @flow
import type {Action} from "@scm-manager/ui-components"; import type { Action } from "@scm-manager/ui-components";
import {apiClient} from "@scm-manager/ui-components"; import { apiClient } from "@scm-manager/ui-components";
import * as types from "../../../modules/types"; import * as types from "../../../modules/types";
import type {Permission, PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types"; import type {
import {isPending} from "../../../modules/pending"; Permission,
import {getFailure} from "../../../modules/failure"; PermissionCollection,
import {Dispatch} from "redux"; 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 = "scm/permissions/FETCH_PERMISSIONS";
export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${ export const FETCH_PERMISSIONS_PENDING = `${FETCH_PERMISSIONS}_${
@@ -141,13 +145,8 @@ export function modifyPermission(
callback(); callback();
} }
}) })
.catch(cause => { .catch(err => {
const error = new Error( dispatch(modifyPermissionFailure(permission, err, namespace, repoName));
`failed to modify permission: ${cause.message}`
);
dispatch(
modifyPermissionFailure(permission, error, namespace, repoName)
);
}); });
}; };
} }
@@ -241,15 +240,7 @@ export function createPermission(
} }
}) })
.catch(err => .catch(err =>
dispatch( dispatch(createPermissionFailure(err, namespace, repoName))
createPermissionFailure(
new Error(
`failed to add permission ${permission.name}: ${err.message}`
),
namespace,
repoName
)
)
); );
}; };
} }
@@ -318,13 +309,8 @@ export function deletePermission(
callback(); callback();
} }
}) })
.catch(cause => { .catch(err => {
const error = new Error( dispatch(deletePermissionFailure(permission, namespace, repoName, err));
`could not delete permission ${permission.name}: ${cause.message}`
);
dispatch(
deletePermissionFailure(permission, namespace, repoName, error)
);
}); });
}; };
} }

View File

@@ -119,7 +119,9 @@ class FileTree extends React.Component<Props> {
<th className="is-hidden-mobile"> <th className="is-hidden-mobile">
{t("sources.file-tree.lastModified")} {t("sources.file-tree.lastModified")}
</th> </th>
<th>{t("sources.file-tree.description")}</th> <th className="is-hidden-mobile">
{t("sources.file-tree.description")}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -6,10 +6,14 @@ import FileSize from "./FileSize";
import FileIcon from "./FileIcon"; import FileIcon from "./FileIcon";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import type { File } from "@scm-manager/ui-types"; import type { File } from "@scm-manager/ui-types";
import classNames from "classnames";
const styles = { const styles = {
iconColumn: { iconColumn: {
width: "16px" width: "16px"
},
wordBreakMinWidth: {
minWidth: "10em"
} }
}; };
@@ -71,12 +75,14 @@ class FileTreeLeaf extends React.Component<Props> {
return ( return (
<tr> <tr>
<td className={classes.iconColumn}>{this.createFileIcon(file)}</td> <td className={classes.iconColumn}>{this.createFileIcon(file)}</td>
<td>{this.createFileName(file)}</td> <td className={classNames(classes.wordBreakMinWidth, "is-word-break")}>{this.createFileName(file)}</td>
<td className="is-hidden-mobile">{fileSize}</td> <td className="is-hidden-mobile">{fileSize}</td>
<td className="is-hidden-mobile"> <td className="is-hidden-mobile">
<DateFromNow date={file.lastModified} /> <DateFromNow date={file.lastModified} />
</td> </td>
<td>{file.description}</td> <td className={classNames(classes.wordBreakMinWidth, "is-word-break", "is-hidden-mobile")}>
{file.description}
</td>
</tr> </tr>
); );
} }

View File

@@ -93,7 +93,7 @@ class Content extends React.Component<Props, State> {
classes.marginInHeader classes.marginInHeader
)} )}
/> />
<span>{file.name}</span> <span className="is-word-break">{file.name}</span>
</div> </div>
<div className="media-right">{selector}</div> <div className="media-right">{selector}</div>
</article> </article>
@@ -125,11 +125,11 @@ class Content extends React.Component<Props, State> {
<tbody> <tbody>
<tr> <tr>
<td>{t("sources.content.path")}</td> <td>{t("sources.content.path")}</td>
<td>{file.path}</td> <td className="is-word-break">{file.path}</td>
</tr> </tr>
<tr> <tr>
<td>{t("sources.content.branch")}</td> <td>{t("sources.content.branch")}</td>
<td>{revision}</td> <td className="is-word-break">{revision}</td>
</tr> </tr>
<tr> <tr>
<td>{t("sources.content.size")}</td> <td>{t("sources.content.size")}</td>
@@ -141,7 +141,7 @@ class Content extends React.Component<Props, State> {
</tr> </tr>
<tr> <tr>
<td>{t("sources.content.description")}</td> <td>{t("sources.content.description")}</td>
<td>{description}</td> <td className="is-word-break">{description}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -25,8 +25,7 @@ export function fetchSources(
dispatch(fetchSourcesSuccess(repository, revision, path, sources)); dispatch(fetchSourcesSuccess(repository, revision, path, sources));
}) })
.catch(err => { .catch(err => {
const error = new Error(`failed to fetch sources: ${err.message}`); dispatch(fetchSourcesFailure(repository, revision, path, err));
dispatch(fetchSourcesFailure(repository, revision, path, error));
}); });
}; };
} }

View File

@@ -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"; const CONTENT_TYPE_USER = "application/vnd.scmm-user+json;v=2";
// TODO i18n for error messages
// fetch users // fetch users
export function fetchUsers(link: string) { export function fetchUsers(link: string) {
@@ -57,9 +55,8 @@ export function fetchUsersByLink(link: string) {
.then(data => { .then(data => {
dispatch(fetchUsersSuccess(data)); dispatch(fetchUsersSuccess(data));
}) })
.catch(cause => { .catch(err => {
const error = new Error(`could not fetch users: ${cause.message}`); dispatch(fetchUsersFailure(link, err));
dispatch(fetchUsersFailure(link, error));
}); });
}; };
} }
@@ -108,9 +105,8 @@ function fetchUser(link: string, name: string) {
.then(data => { .then(data => {
dispatch(fetchUserSuccess(data)); dispatch(fetchUserSuccess(data));
}) })
.catch(cause => { .catch(err => {
const error = new Error(`could not fetch user: ${cause.message}`); dispatch(fetchUserFailure(name, err));
dispatch(fetchUserFailure(name, error));
}); });
}; };
} }
@@ -155,13 +151,7 @@ export function createUser(link: string, user: User, callback?: () => void) {
callback(); callback();
} }
}) })
.catch(err => .catch(err => dispatch(createUserFailure(err)));
dispatch(
createUserFailure(
new Error(`failed to add user ${user.name}: ${err.message}`)
)
)
);
}; };
} }
@@ -260,11 +250,8 @@ export function deleteUser(user: User, callback?: () => void) {
callback(); callback();
} }
}) })
.catch(cause => { .catch(err => {
const error = new Error( dispatch(deleteUserFailure(user, err));
`could not delete user ${user.name}: ${cause.message}`
);
dispatch(deleteUserFailure(user, error));
}); });
}; };
} }

View File

@@ -27,6 +27,14 @@ $blue: #33B2E8;
padding: 0 0 0 3.8em !important; 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 { .main {
min-height: calc(100vh - 260px); min-height: calc(100vh - 260px);
} }

View File

@@ -79,14 +79,14 @@ import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.repository.xml.XmlRepositoryDAO; import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.schedule.QuartzScheduler; import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler; import sonia.scm.schedule.Scheduler;
import sonia.scm.security.AccessTokenCookieIssuer;
import sonia.scm.security.AuthorizationChangedEventProducer; import sonia.scm.security.AuthorizationChangedEventProducer;
import sonia.scm.security.CipherHandler; import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil; import sonia.scm.security.CipherUtil;
import sonia.scm.security.ConfigurableLoginAttemptHandler; import sonia.scm.security.ConfigurableLoginAttemptHandler;
import sonia.scm.security.DefaultJwtAccessTokenRefreshStrategy; import sonia.scm.security.DefaultAccessTokenCookieIssuer;
import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.DefaultSecuritySystem; import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.JwtAccessTokenRefreshStrategy;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.security.LoginAttemptHandler; import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.SecuritySystem; import sonia.scm.security.SecuritySystem;
@@ -320,6 +320,7 @@ public class ScmServletModule extends ServletModule
// bind events // bind events
// bind(LastModifiedUpdateListener.class); // bind(LastModifiedUpdateListener.class);
bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class);
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class); bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
} }

View File

@@ -51,12 +51,12 @@ import java.util.concurrent.TimeUnit;
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 2.0.0 * @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; private final ScmConfiguration configuration;
@@ -66,7 +66,7 @@ public final class AccessTokenCookieIssuer {
* @param configuration scm main configuration * @param configuration scm main configuration
*/ */
@Inject @Inject
public AccessTokenCookieIssuer(ScmConfiguration configuration) { public DefaultAccessTokenCookieIssuer(ScmConfiguration configuration) {
this.configuration = configuration; this.configuration = configuration;
} }

View File

@@ -87,6 +87,7 @@ public final class JwtAccessToken implements AccessToken {
return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class)); return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class));
} }
@Override
public Optional<String> getParentKey() { public Optional<String> getParentKey() {
return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString()); return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString());
} }

View File

@@ -18,6 +18,7 @@ import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilder; import sonia.scm.security.AccessTokenBuilder;
import sonia.scm.security.AccessTokenBuilderFactory; import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.AccessTokenCookieIssuer; import sonia.scm.security.AccessTokenCookieIssuer;
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -46,7 +47,7 @@ public class AuthenticationResourceTest {
@Mock @Mock
private AccessTokenBuilder accessTokenBuilder; 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" + private static final String AUTH_JSON_TRILLIAN = "{\n" +
"\t\"cookie\": true,\n" + "\t\"cookie\": true,\n" +

View File

@@ -20,11 +20,11 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class AccessTokenCookieIssuerTest { public class DefaultAccessTokenCookieIssuerTest {
private ScmConfiguration configuration; private ScmConfiguration configuration;
private AccessTokenCookieIssuer issuer; private DefaultAccessTokenCookieIssuer issuer;
@Mock @Mock
private HttpServletRequest request; private HttpServletRequest request;
@@ -41,7 +41,7 @@ public class AccessTokenCookieIssuerTest {
@Before @Before
public void setUp() { public void setUp() {
configuration = new ScmConfiguration(); configuration = new ScmConfiguration();
issuer = new AccessTokenCookieIssuer(configuration); issuer = new DefaultAccessTokenCookieIssuer(configuration);
} }
@Test @Test