mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-01 19:15:52 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -80,8 +80,20 @@ public interface AccessToken {
|
||||
*/
|
||||
Date getExpiration();
|
||||
|
||||
/**
|
||||
* Returns refresh expiration of token.
|
||||
*
|
||||
* @return refresh expiration
|
||||
*/
|
||||
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
|
||||
* token. For example we could issue a token which can only be used to read a single repository. for more informations
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
25
scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java
Normal file
25
scm-core/src/main/java/sonia/scm/xml/XmlInstantAdapter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<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
|
||||
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<Object> 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<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
}
|
||||
@@ -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<Props> {
|
||||
|
||||
render() {
|
||||
const { t, error } = this.props;
|
||||
if (error) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<strong>{t("error-notification.prefix")}:</strong> {error.message}
|
||||
</Notification>
|
||||
);
|
||||
if (error === UNAUTHORIZED_ERROR) {
|
||||
return (
|
||||
<Notification type="danger">
|
||||
<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;
|
||||
}
|
||||
|
||||
@@ -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, <RepositoryComponent repository={repository}/>);
|
||||
const RepoRoute = ({url, repository}) => {
|
||||
const link = repository._links[linkName].href
|
||||
return this.route(url + to, <RepositoryComponent repository={repository} link={link}/>);
|
||||
};
|
||||
|
||||
// bind config route to extension point
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
}
|
||||
},
|
||||
"error-notification": {
|
||||
"prefix": "Error"
|
||||
"prefix": "Error",
|
||||
"loginLink": "You can login here again.",
|
||||
"timeout": "The session has expired."
|
||||
},
|
||||
"loading": {
|
||||
"alt": "Loading ..."
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class RepositoryRoot extends React.Component<Props> {
|
||||
return (
|
||||
<Page title={repository.namespace + "/" + repository.name}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-quarters">
|
||||
<div className="column is-three-quarters is-clipped">
|
||||
<Switch>
|
||||
<Route
|
||||
path={url}
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,7 +119,9 @@ class FileTree extends React.Component<Props> {
|
||||
<th className="is-hidden-mobile">
|
||||
{t("sources.file-tree.lastModified")}
|
||||
</th>
|
||||
<th>{t("sources.file-tree.description")}</th>
|
||||
<th className="is-hidden-mobile">
|
||||
{t("sources.file-tree.description")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -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<Props> {
|
||||
return (
|
||||
<tr>
|
||||
<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">
|
||||
<DateFromNow date={file.lastModified} />
|
||||
</td>
|
||||
<td>{file.description}</td>
|
||||
<td className={classNames(classes.wordBreakMinWidth, "is-word-break", "is-hidden-mobile")}>
|
||||
{file.description}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class Content extends React.Component<Props, State> {
|
||||
classes.marginInHeader
|
||||
)}
|
||||
/>
|
||||
<span>{file.name}</span>
|
||||
<span className="is-word-break">{file.name}</span>
|
||||
</div>
|
||||
<div className="media-right">{selector}</div>
|
||||
</article>
|
||||
@@ -125,11 +125,11 @@ class Content extends React.Component<Props, State> {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("sources.content.path")}</td>
|
||||
<td>{file.path}</td>
|
||||
<td className="is-word-break">{file.path}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.branch")}</td>
|
||||
<td>{revision}</td>
|
||||
<td className="is-word-break">{revision}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.size")}</td>
|
||||
@@ -141,7 +141,7 @@ class Content extends React.Component<Props, State> {
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("sources.content.description")}</td>
|
||||
<td>{description}</td>
|
||||
<td className="is-word-break">{description}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ public final class JwtAccessToken implements AccessToken {
|
||||
return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getParentKey() {
|
||||
return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString());
|
||||
}
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user