mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 16:05:44 +01:00
merge
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,6 +80,8 @@ public interface AccessToken {
|
|||||||
*/
|
*/
|
||||||
Date getExpiration();
|
Date getExpiration();
|
||||||
|
|
||||||
|
Optional<Date> getRefreshExpiration();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -104,4 +107,9 @@ public interface AccessToken {
|
|||||||
* @return compact representation
|
* @return compact representation
|
||||||
*/
|
*/
|
||||||
String compact();
|
String compact();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns read only map of all claim keys with their values.
|
||||||
|
*/
|
||||||
|
Map<String, Object> getClaims();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,12 +74,22 @@ public interface AccessTokenBuilder {
|
|||||||
* Sets the expiration for the token.
|
* Sets the expiration for the token.
|
||||||
*
|
*
|
||||||
* @param count expiration count
|
* @param count expiration count
|
||||||
* @param unit expirtation unit
|
* @param unit expiration unit
|
||||||
*
|
*
|
||||||
* @return {@code this}
|
* @return {@code this}
|
||||||
*/
|
*/
|
||||||
AccessTokenBuilder expiresIn(long count, TimeUnit unit);
|
AccessTokenBuilder expiresIn(long count, TimeUnit unit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time how long this token may be refreshed. Set this to 0 (zero) to disable automatic refresh.
|
||||||
|
*
|
||||||
|
* @param count Time unit count. If set to 0, automatic refresh is disabled.
|
||||||
|
* @param unit time unit
|
||||||
|
*
|
||||||
|
* @return {@code this}
|
||||||
|
*/
|
||||||
|
AccessTokenBuilder refreshableFor(long count, TimeUnit unit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces the permissions of the token by providing a scope.
|
* Reduces the permissions of the token by providing a scope.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.1"
|
"@scm-manager/ui-extensions": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.21"
|
"@scm-manager/ui-bundler": "^0.0.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -707,9 +707,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.21":
|
"@scm-manager/ui-bundler@^0.0.22":
|
||||||
version "0.0.21"
|
version "0.0.22"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.1"
|
"@scm-manager/ui-extensions": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.21"
|
"@scm-manager/ui-bundler": "^0.0.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,9 +641,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.21":
|
"@scm-manager/ui-bundler@^0.0.22":
|
||||||
version "0.0.21"
|
version "0.0.22"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.1"
|
"@scm-manager/ui-extensions": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.21"
|
"@scm-manager/ui-bundler": "^0.0.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,9 +641,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.21":
|
"@scm-manager/ui-bundler@^0.0.22":
|
||||||
version "0.0.21"
|
version "0.0.22"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"eslint-fix": "eslint src --fix"
|
"eslint-fix": "eslint src --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.21",
|
"@scm-manager/ui-bundler": "^0.0.22",
|
||||||
"create-index": "^2.3.0",
|
"create-index": "^2.3.0",
|
||||||
"enzyme": "^3.5.0",
|
"enzyme": "^3.5.0",
|
||||||
"enzyme-adapter-react-16": "^1.3.1",
|
"enzyme-adapter-react-16": "^1.3.1",
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
"react-dom": "^16.5.2",
|
"react-dom": "^16.5.2",
|
||||||
"react-i18next": "^7.11.0",
|
"react-i18next": "^7.11.0",
|
||||||
"react-jss": "^8.6.1",
|
"react-jss": "^8.6.1",
|
||||||
"react-router-dom": "^4.3.1"
|
"react-router-dom": "^4.3.1",
|
||||||
|
"react-select": "^2.1.2"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
|
|||||||
73
scm-ui-components/packages/ui-components/src/Autocomplete.js
Normal file
73
scm-ui-components/packages/ui-components/src/Autocomplete.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import { AsyncCreatable } from "react-select";
|
||||||
|
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
||||||
|
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
loadSuggestions: string => Promise<AutocompleteObject>,
|
||||||
|
valueSelected: SelectValue => void,
|
||||||
|
label: string,
|
||||||
|
helpText?: string,
|
||||||
|
value?: SelectValue,
|
||||||
|
placeholder: string,
|
||||||
|
loadingMessage: string,
|
||||||
|
noOptionsMessage: string
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
type State = {};
|
||||||
|
|
||||||
|
class Autocomplete extends React.Component<Props, State> {
|
||||||
|
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
placeholder: "Type here",
|
||||||
|
loadingMessage: "Loading...",
|
||||||
|
noOptionsMessage: "No suggestion available"
|
||||||
|
};
|
||||||
|
|
||||||
|
handleInputChange = (newValue: SelectValue) => {
|
||||||
|
this.props.valueSelected(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
// We overwrite this to avoid running into a bug (https://github.com/JedWatson/react-select/issues/2944)
|
||||||
|
isValidNewOption = (inputValue: string, selectValue: SelectValue, selectOptions: SelectValue[]) => {
|
||||||
|
const isNotDuplicated = !selectOptions
|
||||||
|
.map(option => option.label)
|
||||||
|
.includes(inputValue);
|
||||||
|
const isNotEmpty = inputValue !== "";
|
||||||
|
return isNotEmpty && isNotDuplicated;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="field">
|
||||||
|
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||||
|
<div className="control">
|
||||||
|
<AsyncCreatable
|
||||||
|
cacheOptions
|
||||||
|
loadOptions={loadSuggestions}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
loadingMessage={() => loadingMessage}
|
||||||
|
noOptionsMessage={() => noOptionsMessage}
|
||||||
|
isValidNewOption={this.isValidNewOption}
|
||||||
|
onCreateOption={value => {
|
||||||
|
this.handleInputChange({
|
||||||
|
label: value,
|
||||||
|
value: { id: value, displayName: value }
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Autocomplete;
|
||||||
@@ -18,7 +18,7 @@ class ErrorNotification extends React.Component<Props> {
|
|||||||
</Notification>
|
</Notification>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return "";
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {mount, shallow} from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import "./tests/enzyme";
|
import "./tests/enzyme";
|
||||||
import "./tests/i18n";
|
import "./tests/i18n";
|
||||||
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
import ReactRouterEnzymeContext from "react-router-enzyme-context";
|
||||||
import Paginator from "./Paginator";
|
import Paginator from "./Paginator";
|
||||||
|
|
||||||
// TODO: Fix tests
|
|
||||||
xdescribe("paginator rendering tests", () => {
|
xdescribe("paginator rendering tests", () => {
|
||||||
|
|
||||||
const options = new ReactRouterEnzymeContext();
|
const options = new ReactRouterEnzymeContext();
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
||||||
|
import Autocomplete from "../Autocomplete";
|
||||||
|
import AddButton from "../buttons/AddButton";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
addEntry: SelectValue => void,
|
||||||
|
disabled: boolean,
|
||||||
|
buttonLabel: string,
|
||||||
|
fieldLabel: string,
|
||||||
|
helpText?: string,
|
||||||
|
loadSuggestions: string => Promise<AutocompleteObject>,
|
||||||
|
placeholder?: string,
|
||||||
|
loadingMessage?: string,
|
||||||
|
noOptionsMessage?: string
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
selectedValue?: SelectValue
|
||||||
|
};
|
||||||
|
|
||||||
|
class AutocompleteAddEntryToTableField extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { selectedValue: undefined };
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
disabled,
|
||||||
|
buttonLabel,
|
||||||
|
fieldLabel,
|
||||||
|
helpText,
|
||||||
|
loadSuggestions,
|
||||||
|
placeholder,
|
||||||
|
loadingMessage,
|
||||||
|
noOptionsMessage
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { selectedValue } = this.state;
|
||||||
|
return (
|
||||||
|
<div className="field">
|
||||||
|
<Autocomplete
|
||||||
|
label={fieldLabel}
|
||||||
|
loadSuggestions={loadSuggestions}
|
||||||
|
valueSelected={this.handleAddEntryChange}
|
||||||
|
helpText={helpText}
|
||||||
|
value={selectedValue}
|
||||||
|
placeholder={placeholder}
|
||||||
|
loadingMessage={loadingMessage}
|
||||||
|
noOptionsMessage={noOptionsMessage}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddButton
|
||||||
|
label={buttonLabel}
|
||||||
|
action={this.addButtonClicked}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addButtonClicked = (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.appendEntry();
|
||||||
|
};
|
||||||
|
|
||||||
|
appendEntry = () => {
|
||||||
|
const { selectedValue } = this.state;
|
||||||
|
if (!selectedValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// $FlowFixMe null is needed to clear the selection; undefined does not work
|
||||||
|
this.setState({ ...this.state, selectedValue: null }, () =>
|
||||||
|
this.props.addEntry(selectedValue)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleAddEntryChange = (selection: SelectValue) => {
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
selectedValue: selection
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutocompleteAddEntryToTableField;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Help from '../Help';
|
import Help from "../Help.js";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label?: string,
|
label?: string,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// @create-index
|
// @create-index
|
||||||
|
|
||||||
export { default as AddEntryToTableField } from "./AddEntryToTableField.js";
|
export { default as AddEntryToTableField } from "./AddEntryToTableField.js";
|
||||||
|
export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEntryToTableField.js";
|
||||||
export { default as Checkbox } from "./Checkbox.js";
|
export { default as Checkbox } from "./Checkbox.js";
|
||||||
export { default as InputField } from "./InputField.js";
|
export { default as InputField } from "./InputField.js";
|
||||||
export { default as Select } from "./Select.js";
|
export { default as Select } from "./Select.js";
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export { default as Help } from "./Help";
|
|||||||
export { default as HelpIcon } from "./HelpIcon";
|
export { default as HelpIcon } from "./HelpIcon";
|
||||||
export { default as Tooltip } from "./Tooltip";
|
export { default as Tooltip } from "./Tooltip";
|
||||||
export { getPageFromMatch } from "./urls";
|
export { getPageFromMatch } from "./urls";
|
||||||
|
export { default as Autocomplete} from "./Autocomplete";
|
||||||
|
|
||||||
export { apiClient, NOT_FOUND_ERROR_MESSAGE, UNAUTHORIZED_ERROR_MESSAGE } from "./apiclient.js";
|
export { apiClient, NOT_FOUND_ERROR_MESSAGE, UNAUTHORIZED_ERROR_MESSAGE } from "./apiclient.js";
|
||||||
|
|
||||||
|
|||||||
@@ -576,6 +576,12 @@
|
|||||||
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
||||||
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.1.2":
|
||||||
|
version "7.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39"
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.12.0"
|
||||||
|
|
||||||
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
|
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
|
||||||
@@ -606,6 +612,46 @@
|
|||||||
lodash "^4.17.10"
|
lodash "^4.17.10"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@emotion/babel-utils@^0.6.4":
|
||||||
|
version "0.6.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/hash" "^0.6.6"
|
||||||
|
"@emotion/memoize" "^0.6.6"
|
||||||
|
"@emotion/serialize" "^0.9.1"
|
||||||
|
convert-source-map "^1.5.1"
|
||||||
|
find-root "^1.1.0"
|
||||||
|
source-map "^0.7.2"
|
||||||
|
|
||||||
|
"@emotion/hash@^0.6.2", "@emotion/hash@^0.6.6":
|
||||||
|
version "0.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.6.6.tgz#62266c5f0eac6941fece302abad69f2ee7e25e44"
|
||||||
|
|
||||||
|
"@emotion/memoize@^0.6.1", "@emotion/memoize@^0.6.6":
|
||||||
|
version "0.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b"
|
||||||
|
|
||||||
|
"@emotion/serialize@^0.9.1":
|
||||||
|
version "0.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.9.1.tgz#a494982a6920730dba6303eb018220a2b629c145"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/hash" "^0.6.6"
|
||||||
|
"@emotion/memoize" "^0.6.6"
|
||||||
|
"@emotion/unitless" "^0.6.7"
|
||||||
|
"@emotion/utils" "^0.8.2"
|
||||||
|
|
||||||
|
"@emotion/stylis@^0.7.0":
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.7.1.tgz#50f63225e712d99e2b2b39c19c70fff023793ca5"
|
||||||
|
|
||||||
|
"@emotion/unitless@^0.6.2", "@emotion/unitless@^0.6.7":
|
||||||
|
version "0.6.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.6.7.tgz#53e9f1892f725b194d5e6a1684a7b394df592397"
|
||||||
|
|
||||||
|
"@emotion/utils@^0.8.2":
|
||||||
|
version "0.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.8.2.tgz#576ff7fb1230185b619a75d258cbc98f0867a8dc"
|
||||||
|
|
||||||
"@gulp-sourcemaps/identity-map@1.X":
|
"@gulp-sourcemaps/identity-map@1.X":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9"
|
resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz#1e6fe5d8027b1f285dc0d31762f566bccd73d5a9"
|
||||||
@@ -641,9 +687,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.21":
|
"@scm-manager/ui-bundler@^0.0.22":
|
||||||
version "0.0.21"
|
version "0.0.22"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
@@ -1121,6 +1167,23 @@ babel-messages@^6.23.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.22.0"
|
||||||
|
|
||||||
|
babel-plugin-emotion@^9.2.11:
|
||||||
|
version "9.2.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz#319c005a9ee1d15bb447f59fe504c35fd5807728"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-module-imports" "^7.0.0"
|
||||||
|
"@emotion/babel-utils" "^0.6.4"
|
||||||
|
"@emotion/hash" "^0.6.2"
|
||||||
|
"@emotion/memoize" "^0.6.1"
|
||||||
|
"@emotion/stylis" "^0.7.0"
|
||||||
|
babel-plugin-macros "^2.0.0"
|
||||||
|
babel-plugin-syntax-jsx "^6.18.0"
|
||||||
|
convert-source-map "^1.5.0"
|
||||||
|
find-root "^1.1.0"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
source-map "^0.5.7"
|
||||||
|
touch "^2.0.1"
|
||||||
|
|
||||||
babel-plugin-istanbul@^4.1.6:
|
babel-plugin-istanbul@^4.1.6:
|
||||||
version "4.1.6"
|
version "4.1.6"
|
||||||
resolved "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
|
resolved "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
|
||||||
@@ -1134,6 +1197,17 @@ babel-plugin-jest-hoist@^23.2.0:
|
|||||||
version "23.2.0"
|
version "23.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
|
resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
|
||||||
|
|
||||||
|
babel-plugin-macros@^2.0.0:
|
||||||
|
version "2.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.4.2.tgz#21b1a2e82e2130403c5ff785cba6548e9b644b28"
|
||||||
|
dependencies:
|
||||||
|
cosmiconfig "^5.0.5"
|
||||||
|
resolve "^1.8.1"
|
||||||
|
|
||||||
|
babel-plugin-syntax-jsx@^6.18.0:
|
||||||
|
version "6.18.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
|
||||||
|
|
||||||
babel-plugin-syntax-object-rest-spread@^6.13.0:
|
babel-plugin-syntax-object-rest-spread@^6.13.0:
|
||||||
version "6.13.0"
|
version "6.13.0"
|
||||||
resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
||||||
@@ -1633,12 +1707,24 @@ cached-path-relative@^1.0.0:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
|
resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
|
||||||
|
|
||||||
|
caller-callsite@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
|
||||||
|
dependencies:
|
||||||
|
callsites "^2.0.0"
|
||||||
|
|
||||||
caller-path@^0.1.0:
|
caller-path@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites "^0.2.0"
|
callsites "^0.2.0"
|
||||||
|
|
||||||
|
caller-path@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
|
||||||
|
dependencies:
|
||||||
|
caller-callsite "^2.0.0"
|
||||||
|
|
||||||
callsite@1.0.0:
|
callsite@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
|
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
|
||||||
@@ -1782,7 +1868,7 @@ class-utils@^0.3.5:
|
|||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
classnames@^2.2.6:
|
classnames@^2.2.5, classnames@^2.2.6:
|
||||||
version "2.2.6"
|
version "2.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
|
|
||||||
@@ -1966,7 +2052,7 @@ contains-path@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
||||||
|
|
||||||
convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1:
|
convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1992,6 +2078,15 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
|
||||||
|
cosmiconfig@^5.0.5:
|
||||||
|
version "5.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04"
|
||||||
|
dependencies:
|
||||||
|
import-fresh "^2.0.0"
|
||||||
|
is-directory "^0.3.1"
|
||||||
|
js-yaml "^3.9.0"
|
||||||
|
parse-json "^4.0.0"
|
||||||
|
|
||||||
coveralls@^2.11.3:
|
coveralls@^2.11.3:
|
||||||
version "2.13.3"
|
version "2.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.3.tgz#9ad7c2ae527417f361e8b626483f48ee92dd2bc7"
|
resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.3.tgz#9ad7c2ae527417f361e8b626483f48ee92dd2bc7"
|
||||||
@@ -2009,6 +2104,18 @@ create-ecdh@^4.0.0:
|
|||||||
bn.js "^4.1.0"
|
bn.js "^4.1.0"
|
||||||
elliptic "^6.0.0"
|
elliptic "^6.0.0"
|
||||||
|
|
||||||
|
create-emotion@^9.2.12:
|
||||||
|
version "9.2.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-9.2.12.tgz#0fc8e7f92c4f8bb924b0fef6781f66b1d07cb26f"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/hash" "^0.6.2"
|
||||||
|
"@emotion/memoize" "^0.6.1"
|
||||||
|
"@emotion/stylis" "^0.7.0"
|
||||||
|
"@emotion/unitless" "^0.6.2"
|
||||||
|
csstype "^2.5.2"
|
||||||
|
stylis "^3.5.0"
|
||||||
|
stylis-rule-sheet "^0.0.10"
|
||||||
|
|
||||||
create-hash@^1.1.0, create-hash@^1.1.2:
|
create-hash@^1.1.0, create-hash@^1.1.2:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
resolved "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||||
@@ -2122,6 +2229,10 @@ cssstyle@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cssom "0.3.x"
|
cssom "0.3.x"
|
||||||
|
|
||||||
|
csstype@^2.5.2:
|
||||||
|
version "2.5.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff"
|
||||||
|
|
||||||
d@1:
|
d@1:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
|
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
|
||||||
@@ -2362,6 +2473,12 @@ doctrine@^2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
|
dom-helpers@^3.3.1:
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.1.2"
|
||||||
|
|
||||||
dom-serializer@0, dom-serializer@~0.1.0:
|
dom-serializer@0, dom-serializer@~0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
||||||
@@ -2466,6 +2583,13 @@ emoji-regex@^6.5.1:
|
|||||||
version "6.5.1"
|
version "6.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
|
||||||
|
|
||||||
|
emotion@^9.1.2:
|
||||||
|
version "9.2.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.12.tgz#53925aaa005614e65c6e43db8243c843574d1ea9"
|
||||||
|
dependencies:
|
||||||
|
babel-plugin-emotion "^9.2.11"
|
||||||
|
create-emotion "^9.2.12"
|
||||||
|
|
||||||
encodeurl@~1.0.1, encodeurl@~1.0.2:
|
encodeurl@~1.0.1, encodeurl@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||||
@@ -2561,7 +2685,7 @@ enzyme@^3.5.0:
|
|||||||
rst-selector-parser "^2.2.3"
|
rst-selector-parser "^2.2.3"
|
||||||
string.prototype.trim "^1.1.2"
|
string.prototype.trim "^1.1.2"
|
||||||
|
|
||||||
error-ex@^1.2.0:
|
error-ex@^1.2.0, error-ex@^1.3.1:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3070,6 +3194,10 @@ find-node-modules@^1.0.4:
|
|||||||
findup-sync "0.4.2"
|
findup-sync "0.4.2"
|
||||||
merge "^1.2.0"
|
merge "^1.2.0"
|
||||||
|
|
||||||
|
find-root@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
|
||||||
|
|
||||||
find-up@^1.0.0:
|
find-up@^1.0.0:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
||||||
@@ -3862,6 +3990,13 @@ immutable@^3:
|
|||||||
version "3.8.2"
|
version "3.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
||||||
|
|
||||||
|
import-fresh@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
|
||||||
|
dependencies:
|
||||||
|
caller-path "^2.0.0"
|
||||||
|
resolve-from "^3.0.0"
|
||||||
|
|
||||||
import-local@^1.0.0:
|
import-local@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
|
resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
|
||||||
@@ -4038,6 +4173,10 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
|
|||||||
is-data-descriptor "^1.0.0"
|
is-data-descriptor "^1.0.0"
|
||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
|
|
||||||
|
is-directory@^0.3.1:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
|
||||||
|
|
||||||
is-dotfile@^1.0.0:
|
is-dotfile@^1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
|
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
|
||||||
@@ -4689,7 +4828,7 @@ js-yaml@3.6.1:
|
|||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^2.6.0"
|
esprima "^2.6.0"
|
||||||
|
|
||||||
js-yaml@^3.12.0, js-yaml@^3.7.0:
|
js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0:
|
||||||
version "3.12.0"
|
version "3.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4743,6 +4882,10 @@ jsesc@~0.5.0:
|
|||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
||||||
|
|
||||||
|
json-parse-better-errors@^1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
||||||
|
|
||||||
json-schema-traverse@^0.3.0:
|
json-schema-traverse@^0.3.0:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
|
||||||
@@ -5127,7 +5270,7 @@ log-driver@1.2.5:
|
|||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056"
|
resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056"
|
||||||
|
|
||||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
|
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5216,6 +5359,10 @@ mem@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^1.0.0"
|
mimic-fn "^1.0.0"
|
||||||
|
|
||||||
|
memoize-one@^4.0.0:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.0.3.tgz#cdfdd942853f1a1b4c71c5336b8c49da0bf0273c"
|
||||||
|
|
||||||
memoizee@0.4.X:
|
memoizee@0.4.X:
|
||||||
version "0.4.14"
|
version "0.4.14"
|
||||||
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
|
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
|
||||||
@@ -5556,6 +5703,12 @@ nopt@^4.0.1:
|
|||||||
abbrev "1"
|
abbrev "1"
|
||||||
osenv "^0.1.4"
|
osenv "^0.1.4"
|
||||||
|
|
||||||
|
nopt@~1.0.10:
|
||||||
|
version "1.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
||||||
|
dependencies:
|
||||||
|
abbrev "1"
|
||||||
|
|
||||||
normalize-package-data@^2.3.2:
|
normalize-package-data@^2.3.2:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
|
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
|
||||||
@@ -5907,6 +6060,13 @@ parse-json@^2.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
error-ex "^1.2.0"
|
error-ex "^1.2.0"
|
||||||
|
|
||||||
|
parse-json@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
|
||||||
|
dependencies:
|
||||||
|
error-ex "^1.3.1"
|
||||||
|
json-parse-better-errors "^1.0.1"
|
||||||
|
|
||||||
parse-passwd@^1.0.0:
|
parse-passwd@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||||
@@ -6279,6 +6439,12 @@ react-i18next@^7.11.0:
|
|||||||
html-parse-stringify2 "2.0.1"
|
html-parse-stringify2 "2.0.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
|
react-input-autosize@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.5.8"
|
||||||
|
|
||||||
react-is@^16.5.2:
|
react-is@^16.5.2:
|
||||||
version "16.5.2"
|
version "16.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
||||||
@@ -6293,6 +6459,10 @@ react-jss@^8.6.1:
|
|||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
theming "^1.3.0"
|
theming "^1.3.0"
|
||||||
|
|
||||||
|
react-lifecycles-compat@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
|
||||||
react-router-dom@^4.3.1:
|
react-router-dom@^4.3.1:
|
||||||
version "4.3.1"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
|
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6"
|
||||||
@@ -6323,6 +6493,18 @@ react-router@^4.3.1:
|
|||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
warning "^4.0.1"
|
warning "^4.0.1"
|
||||||
|
|
||||||
|
react-select@^2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.1.2.tgz#7a3e4c2b9efcd8c44ae7cf6ebb8b060ef69c513c"
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.5"
|
||||||
|
emotion "^9.1.2"
|
||||||
|
memoize-one "^4.0.0"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
raf "^3.4.0"
|
||||||
|
react-input-autosize "^2.2.1"
|
||||||
|
react-transition-group "^2.2.1"
|
||||||
|
|
||||||
react-test-renderer@^16.0.0-0:
|
react-test-renderer@^16.0.0-0:
|
||||||
version "16.5.2"
|
version "16.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.2.tgz#92e9d2c6f763b9821b2e0b22f994ee675068b5ae"
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.2.tgz#92e9d2c6f763b9821b2e0b22f994ee675068b5ae"
|
||||||
@@ -6332,6 +6514,15 @@ react-test-renderer@^16.0.0-0:
|
|||||||
react-is "^16.5.2"
|
react-is "^16.5.2"
|
||||||
schedule "^0.5.0"
|
schedule "^0.5.0"
|
||||||
|
|
||||||
|
react-transition-group@^2.2.1:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874"
|
||||||
|
dependencies:
|
||||||
|
dom-helpers "^3.3.1"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
react@^16.4.2:
|
react@^16.4.2:
|
||||||
version "16.6.0"
|
version "16.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.6.0.tgz#b34761cfaf3e30f5508bc732fb4736730b7da246"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.6.0.tgz#b34761cfaf3e30f5508bc732fb4736730b7da246"
|
||||||
@@ -6466,6 +6657,10 @@ regenerator-runtime@^0.11.0:
|
|||||||
version "0.11.1"
|
version "0.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||||
|
|
||||||
|
regenerator-runtime@^0.12.0:
|
||||||
|
version "0.12.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||||
|
|
||||||
regenerator-transform@^0.13.3:
|
regenerator-transform@^0.13.3:
|
||||||
version "0.13.3"
|
version "0.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
|
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
|
||||||
@@ -6659,7 +6854,7 @@ resolve@1.1.7:
|
|||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||||
|
|
||||||
resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
|
resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0, resolve@^1.8.1:
|
||||||
version "1.8.1"
|
version "1.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7033,6 +7228,10 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
|||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
|
|
||||||
|
source-map@^0.7.2:
|
||||||
|
version "0.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||||
|
|
||||||
sparkles@^1.0.0:
|
sparkles@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
|
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
|
||||||
@@ -7243,6 +7442,14 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
|||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
|
|
||||||
|
stylis-rule-sheet@^0.0.10:
|
||||||
|
version "0.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430"
|
||||||
|
|
||||||
|
stylis@^3.5.0:
|
||||||
|
version "3.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
|
||||||
|
|
||||||
subarg@^1.0.0:
|
subarg@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
|
resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
|
||||||
@@ -7442,6 +7649,12 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
|||||||
regex-not "^1.0.2"
|
regex-not "^1.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
|
touch@^2.0.1:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/touch/-/touch-2.0.2.tgz#ca0b2a3ae3211246a61b16ba9e6cbf1596287164"
|
||||||
|
dependencies:
|
||||||
|
nopt "~1.0.10"
|
||||||
|
|
||||||
tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
|
tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
|
||||||
version "2.4.3"
|
version "2.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"check": "flow check"
|
"check": "flow check"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.21"
|
"@scm-manager/ui-bundler": "^0.0.22"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
|
|||||||
10
scm-ui-components/packages/ui-types/src/Autocomplete.js
Normal file
10
scm-ui-components/packages/ui-types/src/Autocomplete.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// @flow
|
||||||
|
export type AutocompleteObject = {
|
||||||
|
id: string,
|
||||||
|
displayName: string
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SelectValue = {
|
||||||
|
value: AutocompleteObject,
|
||||||
|
label: string
|
||||||
|
};
|
||||||
@@ -22,3 +22,5 @@ export type { IndexResources } from "./IndexResources";
|
|||||||
export type { Permission, PermissionCreateEntry, PermissionCollection } from "./RepositoryPermissions";
|
export type { Permission, PermissionCreateEntry, PermissionCollection } from "./RepositoryPermissions";
|
||||||
|
|
||||||
export type { SubRepository, File } from "./Sources";
|
export type { SubRepository, File } from "./Sources";
|
||||||
|
|
||||||
|
export type { SelectValue, AutocompleteObject } from "./Autocomplete";
|
||||||
|
|||||||
@@ -707,9 +707,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.21":
|
"@scm-manager/ui-bundler@^0.0.22":
|
||||||
version "0.0.21"
|
version "0.0.22"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-router-redux": "^5.0.0-alpha.9",
|
"react-router-redux": "^5.0.0-alpha.9",
|
||||||
|
"react-select": "^2.1.2",
|
||||||
"react-syntax-highlighter": "^9.0.1",
|
"react-syntax-highlighter": "^9.0.1",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
"redux-devtools-extension": "^2.13.5",
|
"redux-devtools-extension": "^2.13.5",
|
||||||
@@ -51,7 +52,7 @@
|
|||||||
"pre-commit": "jest && flow && eslint src"
|
"pre-commit": "jest && flow && eslint src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.21",
|
"@scm-manager/ui-bundler": "^0.0.22",
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"copyfiles": "^2.0.0",
|
"copyfiles": "^2.0.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
|
|||||||
@@ -39,7 +39,13 @@
|
|||||||
"label": "Add member",
|
"label": "Add member",
|
||||||
"error": "Invalid member name"
|
"error": "Invalid member name"
|
||||||
},
|
},
|
||||||
"group-form": {
|
"add-member-autocomplete": {
|
||||||
|
"placeholder": "Enter member",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"no-options": "No suggestion available"
|
||||||
|
},
|
||||||
|
|
||||||
|
"group-form": {
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"name-error": "Group name is invalid",
|
"name-error": "Group name is invalid",
|
||||||
"description-error": "Description is invalid",
|
"description-error": "Description is invalid",
|
||||||
|
|||||||
@@ -84,11 +84,14 @@
|
|||||||
"label": "Branches"
|
"label": "Branches"
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
|
"user": "User",
|
||||||
|
"group": "Group",
|
||||||
"error-title": "Error",
|
"error-title": "Error",
|
||||||
"error-subtitle": "Unknown permissions error",
|
"error-subtitle": "Unknown permissions error",
|
||||||
"name": "User or Group",
|
"name": "User or Group",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"group-permission": "Group Permission",
|
"group-permission": "Group Permission",
|
||||||
|
"user-permission": "User Permission",
|
||||||
"edit-permission": {
|
"edit-permission": {
|
||||||
"delete-button": "Delete",
|
"delete-button": "Delete",
|
||||||
"save-button": "Save Changes"
|
"save-button": "Save Changes"
|
||||||
@@ -111,6 +114,13 @@
|
|||||||
"groupPermissionHelpText": "States if a permission is a group permission.",
|
"groupPermissionHelpText": "States if a permission is a group permission.",
|
||||||
"nameHelpText": "Manage permissions for a specific user or group",
|
"nameHelpText": "Manage permissions for a specific user or group",
|
||||||
"typeHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions"
|
"typeHelpText": "READ = read; WRITE = read and write; OWNER = read, write and also the ability to manage the properties and permissions"
|
||||||
|
},
|
||||||
|
"autocomplete": {
|
||||||
|
"no-group-options": "No group suggestion available",
|
||||||
|
"group-placeholder": "Enter group",
|
||||||
|
"no-user-options": "No user suggestion available",
|
||||||
|
"user-placeholder": "Enter user",
|
||||||
|
"loading": "Loading..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
|
AutocompleteAddEntryToTableField,
|
||||||
InputField,
|
InputField,
|
||||||
SubmitButton,
|
SubmitButton,
|
||||||
Textarea,
|
Textarea
|
||||||
AddEntryToTableField
|
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import type { Group } from "@scm-manager/ui-types";
|
import type { Group, SelectValue } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
import * as validator from "./groupValidation";
|
import * as validator from "./groupValidation";
|
||||||
import MemberNameTable from "./MemberNameTable";
|
import MemberNameTable from "./MemberNameTable";
|
||||||
@@ -16,7 +16,8 @@ type Props = {
|
|||||||
t: string => string,
|
t: string => string,
|
||||||
submitForm: Group => void,
|
submitForm: Group => void,
|
||||||
loading?: boolean,
|
loading?: boolean,
|
||||||
group?: Group
|
group?: Group,
|
||||||
|
loadUserSuggestions: string => any
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -70,7 +71,7 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t, loading } = this.props;
|
const { t, loading } = this.props;
|
||||||
const group = this.state.group;
|
const { group } = this.state;
|
||||||
let nameField = null;
|
let nameField = null;
|
||||||
if (!this.props.group) {
|
if (!this.props.group) {
|
||||||
nameField = (
|
nameField = (
|
||||||
@@ -97,15 +98,20 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
helpText={t("group-form.help.descriptionHelpText")}
|
helpText={t("group-form.help.descriptionHelpText")}
|
||||||
/>
|
/>
|
||||||
<MemberNameTable
|
<MemberNameTable
|
||||||
members={this.state.group.members}
|
members={group.members}
|
||||||
memberListChanged={this.memberListChanged}
|
memberListChanged={this.memberListChanged}
|
||||||
/>
|
/>
|
||||||
<AddEntryToTableField
|
|
||||||
|
<AutocompleteAddEntryToTableField
|
||||||
addEntry={this.addMember}
|
addEntry={this.addMember}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
buttonLabel={t("add-member-button.label")}
|
buttonLabel={t("add-member-button.label")}
|
||||||
fieldLabel={t("add-member-textfield.label")}
|
fieldLabel={t("add-member-textfield.label")}
|
||||||
errorMessage={t("add-member-textfield.error")}
|
errorMessage={t("add-member-textfield.error")}
|
||||||
|
loadSuggestions={this.props.loadUserSuggestions}
|
||||||
|
placeholder={t("add-member-autocomplete.placeholder")}
|
||||||
|
loadingMessage={t("add-member-autocomplete.loading")}
|
||||||
|
noOptionsMessage={t("add-member-autocomplete.no-options")}
|
||||||
/>
|
/>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
disabled={!this.isValid()}
|
disabled={!this.isValid()}
|
||||||
@@ -126,8 +132,8 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
addMember = (membername: string) => {
|
addMember = (value: SelectValue) => {
|
||||||
if (this.isMember(membername)) {
|
if (this.isMember(value.value.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +141,7 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
...this.state,
|
...this.state,
|
||||||
group: {
|
group: {
|
||||||
...this.state.group,
|
...this.state.group,
|
||||||
members: [...this.state.group.members, membername]
|
members: [...this.state.group.members, value.value.id]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import {
|
|||||||
} from "../modules/groups";
|
} from "../modules/groups";
|
||||||
import type { Group } from "@scm-manager/ui-types";
|
import type { Group } from "@scm-manager/ui-types";
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import { getGroupsLink } from "../../modules/indexResource";
|
import {
|
||||||
|
getGroupsLink,
|
||||||
|
getUserAutoCompleteLink
|
||||||
|
} from "../../modules/indexResource";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
@@ -22,7 +25,8 @@ type Props = {
|
|||||||
loading?: boolean,
|
loading?: boolean,
|
||||||
error?: Error,
|
error?: Error,
|
||||||
resetForm: () => void,
|
resetForm: () => void,
|
||||||
createLink: string
|
createLink: string,
|
||||||
|
autocompleteLink: string
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {};
|
type State = {};
|
||||||
@@ -31,6 +35,7 @@ class AddGroup extends React.Component<Props, State> {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.resetForm();
|
this.props.resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t, loading, error } = this.props;
|
const { t, loading, error } = this.props;
|
||||||
return (
|
return (
|
||||||
@@ -43,12 +48,26 @@ class AddGroup extends React.Component<Props, State> {
|
|||||||
<GroupForm
|
<GroupForm
|
||||||
submitForm={group => this.createGroup(group)}
|
submitForm={group => this.createGroup(group)}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
loadUserSuggestions={this.loadUserAutocompletion}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadUserAutocompletion = (inputValue: string) => {
|
||||||
|
const url = this.props.autocompleteLink + "?q=";
|
||||||
|
return fetch(url + inputValue)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
return json.map(element => {
|
||||||
|
return {
|
||||||
|
value: element,
|
||||||
|
label: `${element.displayName} (${element.id})`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
groupCreated = () => {
|
groupCreated = () => {
|
||||||
this.props.history.push("/groups");
|
this.props.history.push("/groups");
|
||||||
};
|
};
|
||||||
@@ -71,10 +90,12 @@ const mapStateToProps = state => {
|
|||||||
const loading = isCreateGroupPending(state);
|
const loading = isCreateGroupPending(state);
|
||||||
const error = getCreateGroupFailure(state);
|
const error = getCreateGroupFailure(state);
|
||||||
const createLink = getGroupsLink(state);
|
const createLink = getGroupsLink(state);
|
||||||
|
const autocompleteLink = getUserAutoCompleteLink(state);
|
||||||
return {
|
return {
|
||||||
createLink,
|
createLink,
|
||||||
loading,
|
loading,
|
||||||
error
|
error,
|
||||||
|
autocompleteLink
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,23 @@ import React from "react";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import GroupForm from "../components/GroupForm";
|
import GroupForm from "../components/GroupForm";
|
||||||
import {
|
import {
|
||||||
modifyGroup,
|
getModifyGroupFailure,
|
||||||
modifyGroupReset,
|
|
||||||
isModifyGroupPending,
|
isModifyGroupPending,
|
||||||
getModifyGroupFailure
|
modifyGroup,
|
||||||
|
modifyGroupReset
|
||||||
} from "../modules/groups";
|
} from "../modules/groups";
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import { withRouter } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
import type { Group } from "@scm-manager/ui-types";
|
import type { Group } from "@scm-manager/ui-types";
|
||||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||||
|
import { getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
group: Group,
|
group: Group,
|
||||||
modifyGroup: (group: Group, callback?: () => void) => void,
|
modifyGroup: (group: Group, callback?: () => void) => void,
|
||||||
modifyGroupReset: Group => void,
|
modifyGroupReset: Group => void,
|
||||||
fetchGroup: (name: string) => void,
|
fetchGroup: (name: string) => void,
|
||||||
|
autocompleteLink: string,
|
||||||
history: History,
|
history: History,
|
||||||
loading?: boolean,
|
loading?: boolean,
|
||||||
error: Error
|
error: Error
|
||||||
@@ -37,6 +39,20 @@ class EditGroup extends React.Component<Props> {
|
|||||||
this.props.modifyGroup(group, this.groupModified(group));
|
this.props.modifyGroup(group, this.groupModified(group));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loadUserAutocompletion = (inputValue: string) => {
|
||||||
|
const url = this.props.autocompleteLink + "?q=";
|
||||||
|
return fetch(url + inputValue)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
return json.map(element => {
|
||||||
|
return {
|
||||||
|
value: element,
|
||||||
|
label: `${element.displayName} (${element.id})`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { group, loading, error } = this.props;
|
const { group, loading, error } = this.props;
|
||||||
return (
|
return (
|
||||||
@@ -48,6 +64,7 @@ class EditGroup extends React.Component<Props> {
|
|||||||
this.modifyGroup(group);
|
this.modifyGroup(group);
|
||||||
}}
|
}}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
loadUserSuggestions={this.loadUserAutocompletion}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -57,9 +74,11 @@ class EditGroup extends React.Component<Props> {
|
|||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const loading = isModifyGroupPending(state, ownProps.group.name);
|
const loading = isModifyGroupPending(state, ownProps.group.name);
|
||||||
const error = getModifyGroupFailure(state, ownProps.group.name);
|
const error = getModifyGroupFailure(state, ownProps.group.name);
|
||||||
|
const autocompleteLink = getUserAutoCompleteLink(state);
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
error
|
error,
|
||||||
|
autocompleteLink
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import * as types from "./types";
|
import * as types from "./types";
|
||||||
|
|
||||||
import { apiClient } from "@scm-manager/ui-components";
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
import type { Action, IndexResources } from "@scm-manager/ui-types";
|
import type { Action, IndexResources, Link } from "@scm-manager/ui-types";
|
||||||
import { isPending } from "./pending";
|
import { isPending } from "./pending";
|
||||||
import { getFailure } from "./failure";
|
import { getFailure } from "./failure";
|
||||||
|
|
||||||
@@ -100,6 +100,13 @@ export function getLink(state: Object, name: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLinkCollection(state: Object, name: string): Link[] {
|
||||||
|
if (state.indexResources.links && state.indexResources.links[name]) {
|
||||||
|
return state.indexResources.links[name];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
export function getUiPluginsLink(state: Object) {
|
export function getUiPluginsLink(state: Object) {
|
||||||
return getLink(state, "uiPlugins");
|
return getLink(state, "uiPlugins");
|
||||||
}
|
}
|
||||||
@@ -143,3 +150,23 @@ export function getGitConfigLink(state: Object) {
|
|||||||
export function getSvnConfigLink(state: Object) {
|
export function getSvnConfigLink(state: Object) {
|
||||||
return getLink(state, "svnConfig");
|
return getLink(state, "svnConfig");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUserAutoCompleteLink(state: Object): string {
|
||||||
|
const link = getLinkCollection(state, "autocomplete").find(
|
||||||
|
i => i.name === "users"
|
||||||
|
);
|
||||||
|
if (link) {
|
||||||
|
return link.href;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGroupAutoCompleteLink(state: Object): string {
|
||||||
|
const link = getLinkCollection(state, "autocomplete").find(
|
||||||
|
i => i.name === "groups"
|
||||||
|
);
|
||||||
|
if (link) {
|
||||||
|
return link.href;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ import reducer, {
|
|||||||
getHgConfigLink,
|
getHgConfigLink,
|
||||||
getGitConfigLink,
|
getGitConfigLink,
|
||||||
getSvnConfigLink,
|
getSvnConfigLink,
|
||||||
getLinks, getGroupsLink
|
getLinks,
|
||||||
|
getGroupsLink,
|
||||||
|
getLinkCollection,
|
||||||
|
getUserAutoCompleteLink,
|
||||||
|
getGroupAutoCompleteLink
|
||||||
} from "./indexResource";
|
} from "./indexResource";
|
||||||
|
|
||||||
const indexResourcesUnauthenticated = {
|
const indexResourcesUnauthenticated = {
|
||||||
@@ -73,11 +77,22 @@ const indexResourcesAuthenticated = {
|
|||||||
},
|
},
|
||||||
svnConfig: {
|
svnConfig: {
|
||||||
href: "http://localhost:8081/scm/api/v2/config/svn"
|
href: "http://localhost:8081/scm/api/v2/config/svn"
|
||||||
|
},
|
||||||
|
autocomplete: [
|
||||||
|
{
|
||||||
|
href: "http://localhost:8081/scm/api/v2/autocomplete/users",
|
||||||
|
name: "users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "http://localhost:8081/scm/api/v2/autocomplete/groups",
|
||||||
|
name: "groups"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("fetch index resource", () => {
|
describe("index resource", () => {
|
||||||
|
describe("fetch index resource", () => {
|
||||||
const index_url = "/api/v2/";
|
const index_url = "/api/v2/";
|
||||||
const mockStore = configureMockStore([thunk]);
|
const mockStore = configureMockStore([thunk]);
|
||||||
|
|
||||||
@@ -133,9 +148,9 @@ describe("fetch index resource", () => {
|
|||||||
expect(actions[1].payload).toBeDefined();
|
expect(actions[1].payload).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("index resources reducer", () => {
|
describe("index resources reducer", () => {
|
||||||
it("should return empty object, if state and action is undefined", () => {
|
it("should return empty object, if state and action is undefined", () => {
|
||||||
expect(reducer()).toEqual({});
|
expect(reducer()).toEqual({});
|
||||||
});
|
});
|
||||||
@@ -157,9 +172,9 @@ describe("index resources reducer", () => {
|
|||||||
);
|
);
|
||||||
expect(newState.links).toBe(indexResourcesAuthenticated._links);
|
expect(newState.links).toBe(indexResourcesAuthenticated._links);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("index resources selectors", () => {
|
describe("index resources selectors", () => {
|
||||||
const error = new Error("something goes wrong");
|
const error = new Error("something goes wrong");
|
||||||
|
|
||||||
it("should return true, when fetch index resources is pending", () => {
|
it("should return true, when fetch index resources is pending", () => {
|
||||||
@@ -288,7 +303,9 @@ describe("index resources selectors", () => {
|
|||||||
links: indexResourcesAuthenticated._links
|
links: indexResourcesAuthenticated._links
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
expect(getUsersLink(state)).toBe("http://localhost:8081/scm/api/v2/users/");
|
expect(getUsersLink(state)).toBe(
|
||||||
|
"http://localhost:8081/scm/api/v2/users/"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return undefined for users link when unauthenticated or has not permission to see it", () => {
|
it("should return undefined for users link when unauthenticated or has not permission to see it", () => {
|
||||||
@@ -307,7 +324,9 @@ describe("index resources selectors", () => {
|
|||||||
links: indexResourcesAuthenticated._links
|
links: indexResourcesAuthenticated._links
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
expect(getGroupsLink(state)).toBe("http://localhost:8081/scm/api/v2/groups/");
|
expect(getGroupsLink(state)).toBe(
|
||||||
|
"http://localhost:8081/scm/api/v2/groups/"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return undefined for groups link when unauthenticated or has not permission to see it", () => {
|
it("should return undefined for groups link when unauthenticated or has not permission to see it", () => {
|
||||||
@@ -423,4 +442,39 @@ describe("index resources selectors", () => {
|
|||||||
};
|
};
|
||||||
expect(getSvnConfigLink(state)).toBe(undefined);
|
expect(getSvnConfigLink(state)).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Autocomplete links
|
||||||
|
it("should return link collection", () => {
|
||||||
|
const state = {
|
||||||
|
indexResources: {
|
||||||
|
links: indexResourcesAuthenticated._links
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getLinkCollection(state, "autocomplete")).toEqual(
|
||||||
|
indexResourcesAuthenticated._links.autocomplete
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return user autocomplete link", () => {
|
||||||
|
const state = {
|
||||||
|
indexResources: {
|
||||||
|
links: indexResourcesAuthenticated._links
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getUserAutoCompleteLink(state)).toEqual(
|
||||||
|
"http://localhost:8081/scm/api/v2/autocomplete/users"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return group autocomplete link", () => {
|
||||||
|
const state = {
|
||||||
|
indexResources: {
|
||||||
|
links: indexResourcesAuthenticated._links
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(getGroupAutoCompleteLink(state)).toEqual(
|
||||||
|
"http://localhost:8081/scm/api/v2/autocomplete/groups"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {translate} from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import {Checkbox, InputField, SubmitButton} from "@scm-manager/ui-components";
|
import { Autocomplete, SubmitButton } from "@scm-manager/ui-components";
|
||||||
import TypeSelector from "./TypeSelector";
|
import TypeSelector from "./TypeSelector";
|
||||||
import type {PermissionCollection, PermissionCreateEntry} from "@scm-manager/ui-types";
|
import type {
|
||||||
|
PermissionCollection,
|
||||||
|
PermissionCreateEntry,
|
||||||
|
SelectValue
|
||||||
|
} from "@scm-manager/ui-types";
|
||||||
import * as validator from "./permissionValidation";
|
import * as validator from "./permissionValidation";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
createPermission: (permission: PermissionCreateEntry) => void,
|
createPermission: (permission: PermissionCreateEntry) => void,
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
currentPermissions: PermissionCollection
|
currentPermissions: PermissionCollection,
|
||||||
|
groupAutoCompleteLink: string,
|
||||||
|
userAutoCompleteLink: string
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
name: string,
|
name: string,
|
||||||
type: string,
|
type: string,
|
||||||
groupPermission: boolean,
|
groupPermission: boolean,
|
||||||
valid: boolean
|
valid: boolean,
|
||||||
|
value?: SelectValue
|
||||||
};
|
};
|
||||||
|
|
||||||
class CreatePermissionForm extends React.Component<Props, State> {
|
class CreatePermissionForm extends React.Component<Props, State> {
|
||||||
@@ -28,13 +35,95 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
name: "",
|
name: "",
|
||||||
type: "READ",
|
type: "READ",
|
||||||
groupPermission: false,
|
groupPermission: false,
|
||||||
valid: true
|
valid: true,
|
||||||
|
value: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
permissionScopeChanged = event => {
|
||||||
|
const groupPermission = event.target.value === "GROUP_PERMISSION";
|
||||||
|
this.setState({
|
||||||
|
groupPermission: groupPermission,
|
||||||
|
valid: validator.isPermissionValid(
|
||||||
|
this.state.name,
|
||||||
|
groupPermission,
|
||||||
|
this.props.currentPermissions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
this.setState({ ...this.state, groupPermission });
|
||||||
|
};
|
||||||
|
|
||||||
|
loadUserAutocompletion = (inputValue: string) => {
|
||||||
|
return this.loadAutocompletion(this.props.userAutoCompleteLink, inputValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadGroupAutocompletion = (inputValue: string) => {
|
||||||
|
return this.loadAutocompletion(
|
||||||
|
this.props.groupAutoCompleteLink,
|
||||||
|
inputValue
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
loadAutocompletion(url: string, inputValue: string) {
|
||||||
|
const link = url + "?q=";
|
||||||
|
return fetch(link + inputValue)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
return json.map(element => {
|
||||||
|
const label = element.displayName
|
||||||
|
? `${element.displayName} (${element.id})`
|
||||||
|
: element.id;
|
||||||
|
return {
|
||||||
|
value: element,
|
||||||
|
label
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
renderAutocompletionField = () => {
|
||||||
|
const { t } = this.props;
|
||||||
|
if (this.state.groupPermission) {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
loadSuggestions={this.loadGroupAutocompletion}
|
||||||
|
valueSelected={this.groupOrUserSelected}
|
||||||
|
value={this.state.value}
|
||||||
|
label={t("permission.group")}
|
||||||
|
noOptionsMessage={t("permission.autocomplete.no-group-options")}
|
||||||
|
loadingMessage={t("permission.autocomplete.loading")}
|
||||||
|
placeholder={t("permission.autocomplete.group-placeholder")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
loadSuggestions={this.loadUserAutocompletion}
|
||||||
|
valueSelected={this.groupOrUserSelected}
|
||||||
|
value={this.state.value}
|
||||||
|
label={t("permission.user")}
|
||||||
|
noOptionsMessage={t("permission.autocomplete.no-user-options")}
|
||||||
|
loadingMessage={t("permission.autocomplete.loading")}
|
||||||
|
placeholder={t("permission.autocomplete.user-placeholder")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
groupOrUserSelected = (value: SelectValue) => {
|
||||||
|
this.setState({
|
||||||
|
value,
|
||||||
|
name: value.value.id,
|
||||||
|
valid: validator.isPermissionValid(
|
||||||
|
value.value.id,
|
||||||
|
this.state.groupPermission,
|
||||||
|
this.props.currentPermissions
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t, loading } = this.props;
|
const { t, loading } = this.props;
|
||||||
const { name, type, groupPermission } = this.state;
|
|
||||||
|
const { type } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -42,20 +131,30 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
{t("permission.add-permission.add-permission-heading")}
|
{t("permission.add-permission.add-permission-heading")}
|
||||||
</h2>
|
</h2>
|
||||||
<form onSubmit={this.submit}>
|
<form onSubmit={this.submit}>
|
||||||
<InputField
|
<div className="control">
|
||||||
label={t("permission.name")}
|
<label className="radio">
|
||||||
value={name ? name : ""}
|
<input
|
||||||
onChange={this.handleNameChange}
|
type="radio"
|
||||||
validationError={!this.state.valid}
|
name="permission_scope"
|
||||||
errorMessage={t("permission.add-permission.name-input-invalid")}
|
checked={!this.state.groupPermission}
|
||||||
helpText={t("permission.help.nameHelpText")}
|
value="USER_PERMISSION"
|
||||||
|
onChange={this.permissionScopeChanged}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
{t("permission.user-permission")}
|
||||||
label={t("permission.group-permission")}
|
</label>
|
||||||
checked={groupPermission ? groupPermission : false}
|
<label className="radio">
|
||||||
onChange={this.handleGroupPermissionChange}
|
<input
|
||||||
helpText={t("permission.help.groupPermissionHelpText")}
|
type="radio"
|
||||||
|
name="permission_scope"
|
||||||
|
value="GROUP_PERMISSION"
|
||||||
|
checked={this.state.groupPermission}
|
||||||
|
onChange={this.permissionScopeChanged}
|
||||||
/>
|
/>
|
||||||
|
{t("permission.group-permission")}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{this.renderAutocompletionField()}
|
||||||
|
|
||||||
<TypeSelector
|
<TypeSelector
|
||||||
label={t("permission.type")}
|
label={t("permission.type")}
|
||||||
helpText={t("permission.help.typeHelpText")}
|
helpText={t("permission.help.typeHelpText")}
|
||||||
@@ -96,27 +195,6 @@ class CreatePermissionForm extends React.Component<Props, State> {
|
|||||||
type: type
|
type: type
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNameChange = (name: string) => {
|
|
||||||
this.setState({
|
|
||||||
name: name,
|
|
||||||
valid: validator.isPermissionValid(
|
|
||||||
name,
|
|
||||||
this.state.groupPermission,
|
|
||||||
this.props.currentPermissions
|
|
||||||
)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
handleGroupPermissionChange = (groupPermission: boolean) => {
|
|
||||||
this.setState({
|
|
||||||
groupPermission: groupPermission,
|
|
||||||
valid: validator.isPermissionValid(
|
|
||||||
this.state.name,
|
|
||||||
groupPermission,
|
|
||||||
this.props.currentPermissions
|
|
||||||
)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("repos")(CreatePermissionForm);
|
export default translate("repos")(CreatePermissionForm);
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ import SinglePermission from "./SinglePermission";
|
|||||||
import CreatePermissionForm from "../components/CreatePermissionForm";
|
import CreatePermissionForm from "../components/CreatePermissionForm";
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import { getPermissionsLink } from "../../modules/repos";
|
import { getPermissionsLink } from "../../modules/repos";
|
||||||
|
import {
|
||||||
|
getGroupAutoCompleteLink,
|
||||||
|
getUserAutoCompleteLink
|
||||||
|
} from "../../../modules/indexResource";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
namespace: string,
|
namespace: string,
|
||||||
@@ -37,6 +41,8 @@ type Props = {
|
|||||||
hasPermissionToCreate: boolean,
|
hasPermissionToCreate: boolean,
|
||||||
loadingCreatePermission: boolean,
|
loadingCreatePermission: boolean,
|
||||||
permissionsLink: string,
|
permissionsLink: string,
|
||||||
|
groupAutoCompleteLink: string,
|
||||||
|
userAutoCompleteLink: string,
|
||||||
|
|
||||||
//dispatch functions
|
//dispatch functions
|
||||||
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
fetchPermissions: (link: string, namespace: string, repoName: string) => void,
|
||||||
@@ -92,7 +98,9 @@ class Permissions extends React.Component<Props> {
|
|||||||
namespace,
|
namespace,
|
||||||
repoName,
|
repoName,
|
||||||
loadingCreatePermission,
|
loadingCreatePermission,
|
||||||
hasPermissionToCreate
|
hasPermissionToCreate,
|
||||||
|
userAutoCompleteLink,
|
||||||
|
groupAutoCompleteLink
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
@@ -113,6 +121,8 @@ class Permissions extends React.Component<Props> {
|
|||||||
createPermission={permission => this.createPermission(permission)}
|
createPermission={permission => this.createPermission(permission)}
|
||||||
loading={loadingCreatePermission}
|
loading={loadingCreatePermission}
|
||||||
currentPermissions={permissions}
|
currentPermissions={permissions}
|
||||||
|
userAutoCompleteLink={userAutoCompleteLink}
|
||||||
|
groupAutoCompleteLink={groupAutoCompleteLink}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
@@ -165,6 +175,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
);
|
);
|
||||||
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
const hasPermissionToCreate = hasCreatePermission(state, namespace, repoName);
|
||||||
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
const permissionsLink = getPermissionsLink(state, namespace, repoName);
|
||||||
|
const groupAutoCompleteLink = getGroupAutoCompleteLink(state);
|
||||||
|
const userAutoCompleteLink = getUserAutoCompleteLink(state);
|
||||||
return {
|
return {
|
||||||
namespace,
|
namespace,
|
||||||
repoName,
|
repoName,
|
||||||
@@ -173,7 +185,9 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
permissions,
|
permissions,
|
||||||
hasPermissionToCreate,
|
hasPermissionToCreate,
|
||||||
loadingCreatePermission,
|
loadingCreatePermission,
|
||||||
permissionsLink
|
permissionsLink,
|
||||||
|
groupAutoCompleteLink,
|
||||||
|
userAutoCompleteLink
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -189,7 +203,9 @@ const mapDispatchToProps = dispatch => {
|
|||||||
repoName: string,
|
repoName: string,
|
||||||
callback?: () => void
|
callback?: () => void
|
||||||
) => {
|
) => {
|
||||||
dispatch(createPermission(link, permission, namespace, repoName, callback));
|
dispatch(
|
||||||
|
createPermission(link, permission, namespace, repoName, callback)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
createPermissionReset: (namespace: string, repoName: string) => {
|
createPermissionReset: (namespace: string, repoName: string) => {
|
||||||
dispatch(createPermissionReset(namespace, repoName));
|
dispatch(createPermissionReset(namespace, repoName));
|
||||||
|
|||||||
212
scm-ui/yarn.lock
212
scm-ui/yarn.lock
@@ -583,6 +583,12 @@
|
|||||||
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
||||||
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.1.2":
|
||||||
|
version "7.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39"
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.12.0"
|
||||||
|
|
||||||
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
|
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
|
||||||
@@ -613,6 +619,46 @@
|
|||||||
lodash "^4.17.10"
|
lodash "^4.17.10"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@emotion/babel-utils@^0.6.4":
|
||||||
|
version "0.6.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/hash" "^0.6.6"
|
||||||
|
"@emotion/memoize" "^0.6.6"
|
||||||
|
"@emotion/serialize" "^0.9.1"
|
||||||
|
convert-source-map "^1.5.1"
|
||||||
|
find-root "^1.1.0"
|
||||||
|
source-map "^0.7.2"
|
||||||
|
|
||||||
|
"@emotion/hash@^0.6.2", "@emotion/hash@^0.6.6":
|
||||||
|
version "0.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.6.6.tgz#62266c5f0eac6941fece302abad69f2ee7e25e44"
|
||||||
|
|
||||||
|
"@emotion/memoize@^0.6.1", "@emotion/memoize@^0.6.6":
|
||||||
|
version "0.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b"
|
||||||
|
|
||||||
|
"@emotion/serialize@^0.9.1":
|
||||||
|
version "0.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.9.1.tgz#a494982a6920730dba6303eb018220a2b629c145"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/hash" "^0.6.6"
|
||||||
|
"@emotion/memoize" "^0.6.6"
|
||||||
|
"@emotion/unitless" "^0.6.7"
|
||||||
|
"@emotion/utils" "^0.8.2"
|
||||||
|
|
||||||
|
"@emotion/stylis@^0.7.0":
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.7.1.tgz#50f63225e712d99e2b2b39c19c70fff023793ca5"
|
||||||
|
|
||||||
|
"@emotion/unitless@^0.6.2", "@emotion/unitless@^0.6.7":
|
||||||
|
version "0.6.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.6.7.tgz#53e9f1892f725b194d5e6a1684a7b394df592397"
|
||||||
|
|
||||||
|
"@emotion/utils@^0.8.2":
|
||||||
|
version "0.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.8.2.tgz#576ff7fb1230185b619a75d258cbc98f0867a8dc"
|
||||||
|
|
||||||
"@fortawesome/fontawesome-free@^5.3.1":
|
"@fortawesome/fontawesome-free@^5.3.1":
|
||||||
version "5.3.1"
|
version "5.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.3.1.tgz#5466b8f31c1f493a96754c1426c25796d0633dd9"
|
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.3.1.tgz#5466b8f31c1f493a96754c1426c25796d0633dd9"
|
||||||
@@ -652,9 +698,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.21":
|
"@scm-manager/ui-bundler@^0.0.22":
|
||||||
version "0.0.21"
|
version "0.0.22"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.21.tgz#f8b5fa355415cc67b8aaf8744e1701a299dff647"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
@@ -1144,6 +1190,23 @@ babel-messages@^6.23.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.22.0"
|
||||||
|
|
||||||
|
babel-plugin-emotion@^9.2.11:
|
||||||
|
version "9.2.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz#319c005a9ee1d15bb447f59fe504c35fd5807728"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-module-imports" "^7.0.0"
|
||||||
|
"@emotion/babel-utils" "^0.6.4"
|
||||||
|
"@emotion/hash" "^0.6.2"
|
||||||
|
"@emotion/memoize" "^0.6.1"
|
||||||
|
"@emotion/stylis" "^0.7.0"
|
||||||
|
babel-plugin-macros "^2.0.0"
|
||||||
|
babel-plugin-syntax-jsx "^6.18.0"
|
||||||
|
convert-source-map "^1.5.0"
|
||||||
|
find-root "^1.1.0"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
source-map "^0.5.7"
|
||||||
|
touch "^2.0.1"
|
||||||
|
|
||||||
babel-plugin-istanbul@^4.1.6:
|
babel-plugin-istanbul@^4.1.6:
|
||||||
version "4.1.6"
|
version "4.1.6"
|
||||||
resolved "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
|
resolved "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
|
||||||
@@ -1157,6 +1220,17 @@ babel-plugin-jest-hoist@^23.2.0:
|
|||||||
version "23.2.0"
|
version "23.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
|
resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
|
||||||
|
|
||||||
|
babel-plugin-macros@^2.0.0:
|
||||||
|
version "2.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.4.2.tgz#21b1a2e82e2130403c5ff785cba6548e9b644b28"
|
||||||
|
dependencies:
|
||||||
|
cosmiconfig "^5.0.5"
|
||||||
|
resolve "^1.8.1"
|
||||||
|
|
||||||
|
babel-plugin-syntax-jsx@^6.18.0:
|
||||||
|
version "6.18.0"
|
||||||
|
resolved "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
|
||||||
|
|
||||||
babel-plugin-syntax-object-rest-spread@^6.13.0:
|
babel-plugin-syntax-object-rest-spread@^6.13.0:
|
||||||
version "6.13.0"
|
version "6.13.0"
|
||||||
resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
||||||
@@ -1667,12 +1741,24 @@ cached-path-relative@^1.0.0:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
|
resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
|
||||||
|
|
||||||
|
caller-callsite@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
|
||||||
|
dependencies:
|
||||||
|
callsites "^2.0.0"
|
||||||
|
|
||||||
caller-path@^0.1.0:
|
caller-path@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites "^0.2.0"
|
callsites "^0.2.0"
|
||||||
|
|
||||||
|
caller-path@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
|
||||||
|
dependencies:
|
||||||
|
caller-callsite "^2.0.0"
|
||||||
|
|
||||||
callsite@1.0.0:
|
callsite@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
|
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
|
||||||
@@ -2049,7 +2135,7 @@ contains-path@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
||||||
|
|
||||||
convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1:
|
convert-source-map@1.X, convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2086,6 +2172,15 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
|
||||||
|
cosmiconfig@^5.0.5:
|
||||||
|
version "5.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04"
|
||||||
|
dependencies:
|
||||||
|
import-fresh "^2.0.0"
|
||||||
|
is-directory "^0.3.1"
|
||||||
|
js-yaml "^3.9.0"
|
||||||
|
parse-json "^4.0.0"
|
||||||
|
|
||||||
coveralls@^2.11.3:
|
coveralls@^2.11.3:
|
||||||
version "2.13.3"
|
version "2.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.3.tgz#9ad7c2ae527417f361e8b626483f48ee92dd2bc7"
|
resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.3.tgz#9ad7c2ae527417f361e8b626483f48ee92dd2bc7"
|
||||||
@@ -2103,6 +2198,18 @@ create-ecdh@^4.0.0:
|
|||||||
bn.js "^4.1.0"
|
bn.js "^4.1.0"
|
||||||
elliptic "^6.0.0"
|
elliptic "^6.0.0"
|
||||||
|
|
||||||
|
create-emotion@^9.2.12:
|
||||||
|
version "9.2.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-9.2.12.tgz#0fc8e7f92c4f8bb924b0fef6781f66b1d07cb26f"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/hash" "^0.6.2"
|
||||||
|
"@emotion/memoize" "^0.6.1"
|
||||||
|
"@emotion/stylis" "^0.7.0"
|
||||||
|
"@emotion/unitless" "^0.6.2"
|
||||||
|
csstype "^2.5.2"
|
||||||
|
stylis "^3.5.0"
|
||||||
|
stylis-rule-sheet "^0.0.10"
|
||||||
|
|
||||||
create-hash@^1.1.0, create-hash@^1.1.2:
|
create-hash@^1.1.0, create-hash@^1.1.2:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
resolved "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||||
@@ -2213,6 +2320,10 @@ cssstyle@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cssom "0.3.x"
|
cssom "0.3.x"
|
||||||
|
|
||||||
|
csstype@^2.5.2:
|
||||||
|
version "2.5.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff"
|
||||||
|
|
||||||
currently-unhandled@^0.4.1:
|
currently-unhandled@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
||||||
@@ -2476,6 +2587,12 @@ doctrine@^2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
|
dom-helpers@^3.3.1:
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.1.2"
|
||||||
|
|
||||||
dom-serializer@0, dom-serializer@~0.1.0:
|
dom-serializer@0, dom-serializer@~0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
|
||||||
@@ -2584,6 +2701,13 @@ emoji-regex@^6.5.1:
|
|||||||
version "6.5.1"
|
version "6.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
|
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
|
||||||
|
|
||||||
|
emotion@^9.1.2:
|
||||||
|
version "9.2.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.12.tgz#53925aaa005614e65c6e43db8243c843574d1ea9"
|
||||||
|
dependencies:
|
||||||
|
babel-plugin-emotion "^9.2.11"
|
||||||
|
create-emotion "^9.2.12"
|
||||||
|
|
||||||
encodeurl@~1.0.1, encodeurl@~1.0.2:
|
encodeurl@~1.0.1, encodeurl@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||||
@@ -3205,6 +3329,10 @@ find-node-modules@^1.0.4:
|
|||||||
findup-sync "0.4.2"
|
findup-sync "0.4.2"
|
||||||
merge "^1.2.0"
|
merge "^1.2.0"
|
||||||
|
|
||||||
|
find-root@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
|
||||||
|
|
||||||
find-up@^1.0.0:
|
find-up@^1.0.0:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
||||||
@@ -4096,6 +4224,13 @@ immutable@^3:
|
|||||||
version "3.8.2"
|
version "3.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
||||||
|
|
||||||
|
import-fresh@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
|
||||||
|
dependencies:
|
||||||
|
caller-path "^2.0.0"
|
||||||
|
resolve-from "^3.0.0"
|
||||||
|
|
||||||
import-local@^1.0.0:
|
import-local@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
|
resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
|
||||||
@@ -4297,6 +4432,10 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
|
|||||||
is-data-descriptor "^1.0.0"
|
is-data-descriptor "^1.0.0"
|
||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
|
|
||||||
|
is-directory@^0.3.1:
|
||||||
|
version "0.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
|
||||||
|
|
||||||
is-dotfile@^1.0.0:
|
is-dotfile@^1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
|
resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
|
||||||
@@ -4956,7 +5095,7 @@ js-yaml@3.6.1:
|
|||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^2.6.0"
|
esprima "^2.6.0"
|
||||||
|
|
||||||
js-yaml@^3.12.0, js-yaml@^3.7.0:
|
js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0:
|
||||||
version "3.12.0"
|
version "3.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5431,7 +5570,7 @@ log-driver@1.2.5:
|
|||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056"
|
resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056"
|
||||||
|
|
||||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1:
|
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5542,6 +5681,10 @@ mem@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^1.0.0"
|
mimic-fn "^1.0.0"
|
||||||
|
|
||||||
|
memoize-one@^4.0.0:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.0.3.tgz#cdfdd942853f1a1b4c71c5336b8c49da0bf0273c"
|
||||||
|
|
||||||
memoizee@0.4.X:
|
memoizee@0.4.X:
|
||||||
version "0.4.14"
|
version "0.4.14"
|
||||||
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
|
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
|
||||||
@@ -5959,7 +6102,7 @@ noms@0.0.0:
|
|||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
readable-stream "~1.0.31"
|
readable-stream "~1.0.31"
|
||||||
|
|
||||||
nopt@1.0.10:
|
nopt@1.0.10, nopt@~1.0.10:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6805,6 +6948,12 @@ react-i18next@^7.9.0:
|
|||||||
html-parse-stringify2 "2.0.1"
|
html-parse-stringify2 "2.0.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
|
react-input-autosize@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.5.8"
|
||||||
|
|
||||||
react-is@^16.5.2:
|
react-is@^16.5.2:
|
||||||
version "16.5.2"
|
version "16.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
|
||||||
@@ -6819,6 +6968,10 @@ react-jss@^8.6.0:
|
|||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
theming "^1.3.0"
|
theming "^1.3.0"
|
||||||
|
|
||||||
|
react-lifecycles-compat@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
|
||||||
react-redux@^5.0.7:
|
react-redux@^5.0.7:
|
||||||
version "5.0.7"
|
version "5.0.7"
|
||||||
resolved "http://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
|
resolved "http://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
|
||||||
@@ -6868,6 +7021,18 @@ react-router@^4.2.0, react-router@^4.3.1:
|
|||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
warning "^4.0.1"
|
warning "^4.0.1"
|
||||||
|
|
||||||
|
react-select@^2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.1.2.tgz#7a3e4c2b9efcd8c44ae7cf6ebb8b060ef69c513c"
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.5"
|
||||||
|
emotion "^9.1.2"
|
||||||
|
memoize-one "^4.0.0"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
raf "^3.4.0"
|
||||||
|
react-input-autosize "^2.2.1"
|
||||||
|
react-transition-group "^2.2.1"
|
||||||
|
|
||||||
react-syntax-highlighter@^9.0.1:
|
react-syntax-highlighter@^9.0.1:
|
||||||
version "9.0.1"
|
version "9.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-9.0.1.tgz#cad91692e1976f68290f24762ac3451b1fec3d26"
|
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-9.0.1.tgz#cad91692e1976f68290f24762ac3451b1fec3d26"
|
||||||
@@ -6887,6 +7052,15 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.4.1:
|
|||||||
react-is "^16.5.2"
|
react-is "^16.5.2"
|
||||||
schedule "^0.5.0"
|
schedule "^0.5.0"
|
||||||
|
|
||||||
|
react-transition-group@^2.2.1:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874"
|
||||||
|
dependencies:
|
||||||
|
dom-helpers "^3.3.1"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
react@^16.4.2:
|
react@^16.4.2:
|
||||||
version "16.5.2"
|
version "16.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42"
|
||||||
@@ -7068,6 +7242,10 @@ regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
|
|||||||
version "0.11.1"
|
version "0.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||||
|
|
||||||
|
regenerator-runtime@^0.12.0:
|
||||||
|
version "0.12.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||||
|
|
||||||
regenerator-transform@^0.13.3:
|
regenerator-transform@^0.13.3:
|
||||||
version "0.13.3"
|
version "0.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
|
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
|
||||||
@@ -7286,7 +7464,7 @@ resolve@1.1.7:
|
|||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||||
|
|
||||||
resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0:
|
resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0, resolve@^1.8.1:
|
||||||
version "1.8.1"
|
version "1.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7683,6 +7861,10 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
|||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||||
|
|
||||||
|
source-map@^0.7.2:
|
||||||
|
version "0.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||||
|
|
||||||
space-separated-tokens@^1.0.0:
|
space-separated-tokens@^1.0.0:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412"
|
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.2.tgz#e95ab9d19ae841e200808cd96bc7bd0adbbb3412"
|
||||||
@@ -7933,6 +8115,14 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
|||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
|
|
||||||
|
stylis-rule-sheet@^0.0.10:
|
||||||
|
version "0.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430"
|
||||||
|
|
||||||
|
stylis@^3.5.0:
|
||||||
|
version "3.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
|
||||||
|
|
||||||
subarg@^1.0.0:
|
subarg@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
|
resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
|
||||||
@@ -8135,6 +8325,12 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
|||||||
regex-not "^1.0.2"
|
regex-not "^1.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
|
touch@^2.0.1:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/touch/-/touch-2.0.2.tgz#ca0b2a3ae3211246a61b16ba9e6cbf1596287164"
|
||||||
|
dependencies:
|
||||||
|
nopt "~1.0.10"
|
||||||
|
|
||||||
tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
|
tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
|
||||||
version "2.4.3"
|
version "2.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||||
|
|||||||
@@ -72,8 +72,18 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt</artifactId>
|
<artifactId>jjwt-impl</artifactId>
|
||||||
<version>0.4</version>
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- json -->
|
<!-- json -->
|
||||||
@@ -540,6 +550,7 @@
|
|||||||
<scm.stage>DEVELOPMENT</scm.stage>
|
<scm.stage>DEVELOPMENT</scm.stage>
|
||||||
<scm.home>target/scm-it</scm.home>
|
<scm.home>target/scm-it</scm.home>
|
||||||
<environment.profile>default</environment.profile>
|
<environment.profile>default</environment.profile>
|
||||||
|
<jjwt.version>0.10.5</jjwt.version>
|
||||||
<selenium.version>2.53.1</selenium.version>
|
<selenium.version>2.53.1</selenium.version>
|
||||||
<wagon.version>1.0</wagon.version>
|
<wagon.version>1.0</wagon.version>
|
||||||
<mustache.version>0.8.17</mustache.version>
|
<mustache.version>0.8.17</mustache.version>
|
||||||
|
|||||||
@@ -83,8 +83,10 @@ 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.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;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class DefaultJwtAccessTokenRefreshStrategy extends PercentageJwtAccessTokenRefreshStrategy {
|
||||||
|
public DefaultJwtAccessTokenRefreshStrategy() {
|
||||||
|
super(0.5F);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,9 +31,14 @@
|
|||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jwt implementation of {@link AccessToken}.
|
* Jwt implementation of {@link AccessToken}.
|
||||||
*
|
*
|
||||||
@@ -42,6 +47,8 @@ import java.util.Optional;
|
|||||||
*/
|
*/
|
||||||
public final class JwtAccessToken implements AccessToken {
|
public final class JwtAccessToken implements AccessToken {
|
||||||
|
|
||||||
|
public static final String REFRESHABLE_UNTIL_CLAIM_KEY = "scm-manager.refreshExpiration";
|
||||||
|
public static final String PARENT_TOKEN_ID_CLAIM_KEY = "scm-manager.parentTokenId";
|
||||||
private final Claims claims;
|
private final Claims claims;
|
||||||
private final String compact;
|
private final String compact;
|
||||||
|
|
||||||
@@ -75,6 +82,15 @@ public final class JwtAccessToken implements AccessToken {
|
|||||||
return claims.getExpiration();
|
return claims.getExpiration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Date> getRefreshExpiration() {
|
||||||
|
return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getParentKey() {
|
||||||
|
return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Scope getScope() {
|
public Scope getScope() {
|
||||||
return Scopes.fromClaims(claims);
|
return Scopes.fromClaims(claims);
|
||||||
@@ -91,4 +107,8 @@ public final class JwtAccessToken implements AccessToken {
|
|||||||
return compact;
|
return compact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getClaims() {
|
||||||
|
return Collections.unmodifiableMap(claims);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,12 @@ import com.google.common.collect.Maps;
|
|||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
@@ -61,18 +63,24 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
|
|
||||||
private final KeyGenerator keyGenerator;
|
private final KeyGenerator keyGenerator;
|
||||||
private final SecureKeyResolver keyResolver;
|
private final SecureKeyResolver keyResolver;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
private String subject;
|
private String subject;
|
||||||
private String issuer;
|
private String issuer;
|
||||||
private long expiresIn = 60l;
|
private long expiresIn = 1;
|
||||||
private TimeUnit expiresInUnit = TimeUnit.MINUTES;
|
private TimeUnit expiresInUnit = TimeUnit.HOURS;
|
||||||
|
private long refreshableFor = 12;
|
||||||
|
private TimeUnit refreshableForUnit = TimeUnit.HOURS;
|
||||||
|
private Instant refreshExpiration;
|
||||||
|
private String parentKeyId;
|
||||||
private Scope scope = Scope.empty();
|
private Scope scope = Scope.empty();
|
||||||
|
|
||||||
private final Map<String,Object> custom = Maps.newHashMap();
|
private final Map<String,Object> custom = Maps.newHashMap();
|
||||||
|
|
||||||
JwtAccessTokenBuilder(KeyGenerator keyGenerator, SecureKeyResolver keyResolver) {
|
JwtAccessTokenBuilder(KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Clock clock) {
|
||||||
this.keyGenerator = keyGenerator;
|
this.keyGenerator = keyGenerator;
|
||||||
this.keyResolver = keyResolver;
|
this.keyResolver = keyResolver;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -92,7 +100,7 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtAccessTokenBuilder scope(Scope scope) {
|
public JwtAccessTokenBuilder scope(Scope scope) {
|
||||||
Preconditions.checkArgument(scope != null, "scope can not be null");
|
Preconditions.checkArgument(scope != null, "scope cannot be null");
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -106,8 +114,8 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtAccessTokenBuilder expiresIn(long count, TimeUnit unit) {
|
public JwtAccessTokenBuilder expiresIn(long count, TimeUnit unit) {
|
||||||
Preconditions.checkArgument(count > 0, "expires in must be greater than 0");
|
Preconditions.checkArgument(count > 0, "count must be greater than 0");
|
||||||
Preconditions.checkArgument(unit != null, "unit can not be null");
|
Preconditions.checkArgument(unit != null, "unit cannot be null");
|
||||||
|
|
||||||
this.expiresIn = count;
|
this.expiresIn = count;
|
||||||
this.expiresInUnit = unit;
|
this.expiresInUnit = unit;
|
||||||
@@ -115,6 +123,28 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtAccessTokenBuilder refreshableFor(long count, TimeUnit unit) {
|
||||||
|
Preconditions.checkArgument(count >= 0, "count must be greater or equal to 0");
|
||||||
|
Preconditions.checkArgument(unit != null, "unit cannot be null");
|
||||||
|
|
||||||
|
this.refreshableFor = count;
|
||||||
|
this.refreshableForUnit = unit;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
JwtAccessTokenBuilder refreshExpiration(Instant refreshExpiration) {
|
||||||
|
this.refreshExpiration = refreshExpiration;
|
||||||
|
this.refreshableFor = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JwtAccessTokenBuilder parentKey(String parentKeyId) {
|
||||||
|
this.parentKeyId = parentKeyId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private String getSubject(){
|
private String getSubject(){
|
||||||
if (subject == null) {
|
if (subject == null) {
|
||||||
Subject currentSubject = SecurityUtils.getSubject();
|
Subject currentSubject = SecurityUtils.getSubject();
|
||||||
@@ -139,14 +169,27 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder {
|
|||||||
// add scope to custom claims
|
// add scope to custom claims
|
||||||
Scopes.toClaims(customClaims, scope);
|
Scopes.toClaims(customClaims, scope);
|
||||||
|
|
||||||
Date now = new Date();
|
Instant now = clock.instant();
|
||||||
long expiration = expiresInUnit.toMillis(expiresIn);
|
long expiration = expiresInUnit.toMillis(expiresIn);
|
||||||
|
|
||||||
Claims claims = Jwts.claims(customClaims)
|
Claims claims = Jwts.claims(customClaims)
|
||||||
.setSubject(sub)
|
.setSubject(sub)
|
||||||
.setId(id)
|
.setId(id)
|
||||||
.setIssuedAt(now)
|
.setIssuedAt(Date.from(now))
|
||||||
.setExpiration(new Date(now.getTime() + expiration));
|
.setExpiration(new Date(now.toEpochMilli() + expiration));
|
||||||
|
|
||||||
|
|
||||||
|
if (refreshableFor > 0) {
|
||||||
|
long refreshExpiration = refreshableForUnit.toMillis(refreshableFor);
|
||||||
|
claims.put(JwtAccessToken.REFRESHABLE_UNTIL_CLAIM_KEY, new Date(now.toEpochMilli() + refreshExpiration).getTime());
|
||||||
|
} else if (refreshExpiration != null) {
|
||||||
|
claims.put(JwtAccessToken.REFRESHABLE_UNTIL_CLAIM_KEY, Date.from(refreshExpiration));
|
||||||
|
}
|
||||||
|
if (parentKeyId == null) {
|
||||||
|
claims.put(JwtAccessToken.PARENT_TOKEN_ID_CLAIM_KEY, id);
|
||||||
|
} else {
|
||||||
|
claims.put(JwtAccessToken.PARENT_TOKEN_ID_CLAIM_KEY, parentKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
if ( issuer != null ) {
|
if ( issuer != null ) {
|
||||||
claims.setIssuer(issuer);
|
claims.setIssuer(issuer);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
*/
|
*/
|
||||||
package sonia.scm.security;
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
@@ -46,19 +47,25 @@ public final class JwtAccessTokenBuilderFactory implements AccessTokenBuilderFac
|
|||||||
private final KeyGenerator keyGenerator;
|
private final KeyGenerator keyGenerator;
|
||||||
private final SecureKeyResolver keyResolver;
|
private final SecureKeyResolver keyResolver;
|
||||||
private final Set<AccessTokenEnricher> enrichers;
|
private final Set<AccessTokenEnricher> enrichers;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public JwtAccessTokenBuilderFactory(
|
public JwtAccessTokenBuilderFactory(
|
||||||
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<AccessTokenEnricher> enrichers
|
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<AccessTokenEnricher> enrichers) {
|
||||||
) {
|
this(keyGenerator, keyResolver, enrichers, Clock.systemDefaultZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
JwtAccessTokenBuilderFactory(
|
||||||
|
KeyGenerator keyGenerator, SecureKeyResolver keyResolver, Set<AccessTokenEnricher> enrichers, Clock clock) {
|
||||||
this.keyGenerator = keyGenerator;
|
this.keyGenerator = keyGenerator;
|
||||||
this.keyResolver = keyResolver;
|
this.keyResolver = keyResolver;
|
||||||
this.enrichers = enrichers;
|
this.enrichers = enrichers;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtAccessTokenBuilder create() {
|
public JwtAccessTokenBuilder create() {
|
||||||
JwtAccessTokenBuilder builder = new JwtAccessTokenBuilder(keyGenerator, keyResolver);
|
JwtAccessTokenBuilder builder = new JwtAccessTokenBuilder(keyGenerator, keyResolver, clock);
|
||||||
|
|
||||||
// enrich access token builder
|
// enrich access token builder
|
||||||
enrichers.forEach((enricher) -> {
|
enrichers.forEach((enricher) -> {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
|
@ExtensionPoint(multi = false)
|
||||||
|
public interface JwtAccessTokenRefreshStrategy {
|
||||||
|
boolean shouldBeRefreshed(JwtAccessToken oldToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class JwtAccessTokenRefresher {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JwtAccessTokenRefresher.class);
|
||||||
|
|
||||||
|
private final JwtAccessTokenBuilderFactory builderFactory;
|
||||||
|
private final JwtAccessTokenRefreshStrategy refreshStrategy;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public JwtAccessTokenRefresher(JwtAccessTokenBuilderFactory builderFactory, JwtAccessTokenRefreshStrategy refreshStrategy) {
|
||||||
|
this(builderFactory, refreshStrategy, Clock.systemDefaultZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
JwtAccessTokenRefresher(JwtAccessTokenBuilderFactory builderFactory, JwtAccessTokenRefreshStrategy refreshStrategy, Clock clock) {
|
||||||
|
this.builderFactory = builderFactory;
|
||||||
|
this.refreshStrategy = refreshStrategy;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("squid:S3655") // the refresh expiration cannot be null at the time building the new token, because
|
||||||
|
// we checked this before in tokenCanBeRefreshed
|
||||||
|
public Optional<JwtAccessToken> refresh(JwtAccessToken oldToken) {
|
||||||
|
JwtAccessTokenBuilder builder = builderFactory.create();
|
||||||
|
Map<String, Object> claims = oldToken.getClaims();
|
||||||
|
claims.forEach(builder::custom);
|
||||||
|
|
||||||
|
if (canBeRefreshed(oldToken) && shouldBeRefreshed(oldToken)) {
|
||||||
|
Optional<Object> parentTokenId = oldToken.getCustom("scm-manager.parentTokenId");
|
||||||
|
if (!parentTokenId.isPresent()) {
|
||||||
|
log.warn("no parent token id found in token; could not refresh");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
builder.expiresIn(computeOldExpirationInMillis(oldToken), TimeUnit.MILLISECONDS);
|
||||||
|
builder.parentKey(parentTokenId.get().toString());
|
||||||
|
builder.refreshExpiration(oldToken.getRefreshExpiration().get().toInstant());
|
||||||
|
return Optional.of(builder.build());
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long computeOldExpirationInMillis(JwtAccessToken oldToken) {
|
||||||
|
return oldToken.getExpiration().getTime() - oldToken.getIssuedAt().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canBeRefreshed(JwtAccessToken oldToken) {
|
||||||
|
return tokenIsValid(oldToken) && tokenCanBeRefreshed(oldToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldBeRefreshed(JwtAccessToken oldToken) {
|
||||||
|
return refreshStrategy.shouldBeRefreshed(oldToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tokenCanBeRefreshed(JwtAccessToken oldToken) {
|
||||||
|
return oldToken.getRefreshExpiration().map(this::isAfterNow).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tokenIsValid(JwtAccessToken oldToken) {
|
||||||
|
return isAfterNow(oldToken.getExpiration());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAfterNow(Date expiration) {
|
||||||
|
return expiration.toInstant().isAfter(clock.instant());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
|
||||||
|
public class PercentageJwtAccessTokenRefreshStrategy implements JwtAccessTokenRefreshStrategy {
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
private final float refreshPercentage;
|
||||||
|
|
||||||
|
public PercentageJwtAccessTokenRefreshStrategy(float refreshPercentage) {
|
||||||
|
this(Clock.systemDefaultZone(), refreshPercentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
PercentageJwtAccessTokenRefreshStrategy(Clock clock, float refreshPercentage) {
|
||||||
|
this.clock = clock;
|
||||||
|
this.refreshPercentage = refreshPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldBeRefreshed(JwtAccessToken oldToken) {
|
||||||
|
long liveSpan = oldToken.getExpiration().getTime() - oldToken.getIssuedAt().getTime();
|
||||||
|
long age = clock.instant().toEpochMilli() - oldToken.getIssuedAt().getTime();
|
||||||
|
return (float)age/liveSpan > refreshPercentage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package sonia.scm.web.security;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.Priority;
|
||||||
|
import sonia.scm.filter.Filters;
|
||||||
|
import sonia.scm.filter.WebElement;
|
||||||
|
import sonia.scm.security.AccessToken;
|
||||||
|
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||||
|
import sonia.scm.security.AccessTokenResolver;
|
||||||
|
import sonia.scm.security.BearerToken;
|
||||||
|
import sonia.scm.security.JwtAccessToken;
|
||||||
|
import sonia.scm.security.JwtAccessTokenRefresher;
|
||||||
|
import sonia.scm.web.WebTokenGenerator;
|
||||||
|
import sonia.scm.web.filter.HttpFilter;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static java.util.Optional.empty;
|
||||||
|
import static java.util.Optional.of;
|
||||||
|
|
||||||
|
@Priority(Filters.PRIORITY_POST_AUTHENTICATION)
|
||||||
|
@WebElement(value = Filters.PATTERN_RESTAPI,
|
||||||
|
morePatterns = { Filters.PATTERN_DEBUG })
|
||||||
|
public class TokenRefreshFilter extends HttpFilter {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TokenRefreshFilter.class);
|
||||||
|
|
||||||
|
private final Set<WebTokenGenerator> tokenGenerators;
|
||||||
|
private final JwtAccessTokenRefresher refresher;
|
||||||
|
private final AccessTokenResolver resolver;
|
||||||
|
private final AccessTokenCookieIssuer issuer;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TokenRefreshFilter(Set<WebTokenGenerator> tokenGenerators, JwtAccessTokenRefresher refresher, AccessTokenResolver resolver, AccessTokenCookieIssuer issuer) {
|
||||||
|
this.tokenGenerators = tokenGenerators;
|
||||||
|
this.refresher = refresher;
|
||||||
|
this.resolver = resolver;
|
||||||
|
this.issuer = issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
extractToken(request).ifPresent(token -> examineToken(request, response, token));
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<BearerToken> extractToken(HttpServletRequest request) {
|
||||||
|
for (WebTokenGenerator generator : tokenGenerators) {
|
||||||
|
AuthenticationToken token = generator.createToken(request);
|
||||||
|
if (token instanceof BearerToken) {
|
||||||
|
return of((BearerToken) token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void examineToken(HttpServletRequest request, HttpServletResponse response, BearerToken token) {
|
||||||
|
AccessToken accessToken = resolver.resolve(token);
|
||||||
|
if (accessToken instanceof JwtAccessToken) {
|
||||||
|
refresher.refresh((JwtAccessToken) accessToken)
|
||||||
|
.ifPresent(jwtAccessToken -> refreshToken(request, response, jwtAccessToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshToken(HttpServletRequest request, HttpServletResponse response, JwtAccessToken jwtAccessToken) {
|
||||||
|
LOG.debug("refreshing authentication token");
|
||||||
|
issuer.authenticate(request, response, jwtAccessToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,6 @@ import sonia.scm.user.UserDAO;
|
|||||||
import sonia.scm.user.UserTestData;
|
import sonia.scm.user.UserTestData;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -71,6 +70,7 @@ import static org.junit.Assert.assertThat;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link BearerRealm}.
|
* Unit tests for {@link BearerRealm}.
|
||||||
@@ -256,12 +256,6 @@ private String createCompactToken(String subject, SecureKey key) {
|
|||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecureKey createSecureKey() {
|
|
||||||
byte[] bytes = new byte[32];
|
|
||||||
random.nextBytes(bytes);
|
|
||||||
return new SecureKey(bytes, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resolveKey(SecureKey key) {
|
private void resolveKey(SecureKey key) {
|
||||||
when(
|
when(
|
||||||
keyResolver.resolveSigningKey(
|
keyResolver.resolveSigningKey(
|
||||||
@@ -272,16 +266,13 @@ private String createCompactToken(String subject, SecureKey key) {
|
|||||||
.thenReturn(
|
.thenReturn(
|
||||||
new SecretKeySpec(
|
new SecretKeySpec(
|
||||||
key.getBytes(),
|
key.getBytes(),
|
||||||
SignatureAlgorithm.HS256.getValue()
|
SignatureAlgorithm.HS256.getJcaName()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final SecureRandom random = new SecureRandom();
|
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private DAORealmHelperFactory helperFactory;
|
private DAORealmHelperFactory helperFactory;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -56,6 +55,7 @@ import static org.junit.Assert.assertThat;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Mockito.anyString;
|
import static org.mockito.Mockito.anyString;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for {@link JwtAccessTokenBuilder}.
|
* Unit test for {@link JwtAccessTokenBuilder}.
|
||||||
@@ -162,11 +162,4 @@ public class JwtAccessTokenBuilderTest {
|
|||||||
assertEquals("b", token.getCustom("a").get());
|
assertEquals("b", token.getCustom("a").get());
|
||||||
assertEquals("[\"repo:*\"]", token.getScope().toString());
|
assertEquals("[\"repo:*\"]", token.getScope().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecureKey createSecureKey() {
|
|
||||||
byte[] bytes = new byte[32];
|
|
||||||
new Random().nextBytes(bytes);
|
|
||||||
return new SecureKey(bytes, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.time.Duration.ofMinutes;
|
||||||
|
import static java.time.temporal.ChronoUnit.SECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||||
|
|
||||||
|
@SubjectAware(
|
||||||
|
username = "user",
|
||||||
|
password = "secret",
|
||||||
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
|
)
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class JwtAccessTokenRefresherTest {
|
||||||
|
|
||||||
|
private static final Instant NOW = Instant.now().truncatedTo(SECONDS);
|
||||||
|
private static final Instant TOKEN_CREATION = NOW.minus(ofMinutes(1));
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private SecureKeyResolver keyResolver;
|
||||||
|
@Mock
|
||||||
|
private JwtAccessTokenRefreshStrategy refreshStrategy;
|
||||||
|
@Mock
|
||||||
|
private Clock refreshClock;
|
||||||
|
|
||||||
|
private KeyGenerator keyGenerator = () -> "key";
|
||||||
|
|
||||||
|
private JwtAccessTokenRefresher refresher;
|
||||||
|
private JwtAccessTokenBuilder tokenBuilder;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void initKeyResolver() {
|
||||||
|
when(keyResolver.getSecureKey(any())).thenReturn(createSecureKey());
|
||||||
|
|
||||||
|
Clock creationClock = mock(Clock.class);
|
||||||
|
when(creationClock.instant()).thenReturn(TOKEN_CREATION);
|
||||||
|
tokenBuilder = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, Collections.emptySet(), creationClock).create();
|
||||||
|
|
||||||
|
JwtAccessTokenBuilderFactory refreshBuilderFactory = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, Collections.emptySet(), refreshClock);
|
||||||
|
refresher = new JwtAccessTokenRefresher(refreshBuilderFactory, refreshStrategy, refreshClock);
|
||||||
|
when(refreshClock.instant()).thenReturn(NOW);
|
||||||
|
when(refreshStrategy.shouldBeRefreshed(any())).thenReturn(true);
|
||||||
|
|
||||||
|
// set default expiration values
|
||||||
|
tokenBuilder
|
||||||
|
.expiresIn(5, MINUTES)
|
||||||
|
.refreshableFor(10, MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotRefreshTokenWithDisabledRefresh() {
|
||||||
|
JwtAccessToken oldToken = tokenBuilder
|
||||||
|
.refreshableFor(0, MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||||
|
|
||||||
|
assertThat(refreshedToken).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotRefreshTokenWhenTokenExpired() {
|
||||||
|
Instant afterNormalExpiration = NOW.plus(ofMinutes(6));
|
||||||
|
when(refreshClock.instant()).thenReturn(afterNormalExpiration);
|
||||||
|
JwtAccessToken oldToken = tokenBuilder.build();
|
||||||
|
|
||||||
|
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||||
|
|
||||||
|
assertThat(refreshedToken).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotRefreshTokenWhenRefreshExpired() {
|
||||||
|
Instant afterRefreshExpiration = Instant.now().plus(ofMinutes(2));
|
||||||
|
when(refreshClock.instant()).thenReturn(afterRefreshExpiration);
|
||||||
|
JwtAccessToken oldToken = tokenBuilder
|
||||||
|
.refreshableFor(1, MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||||
|
|
||||||
|
assertThat(refreshedToken).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotRefreshTokenWhenStrategyDoesNotSaySo() {
|
||||||
|
JwtAccessToken oldToken = tokenBuilder.build();
|
||||||
|
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(false);
|
||||||
|
|
||||||
|
Optional<JwtAccessToken> refreshedToken = refresher.refresh(oldToken);
|
||||||
|
|
||||||
|
assertThat(refreshedToken).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRefreshTokenWithParentId() {
|
||||||
|
JwtAccessToken oldToken = tokenBuilder.build();
|
||||||
|
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true);
|
||||||
|
|
||||||
|
Optional<JwtAccessToken> refreshedTokenResult = refresher.refresh(oldToken);
|
||||||
|
|
||||||
|
assertThat(refreshedTokenResult).isNotEmpty();
|
||||||
|
JwtAccessToken refreshedToken = refreshedTokenResult.get();
|
||||||
|
assertThat(refreshedToken.getParentKey()).get().isEqualTo("key");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRefreshTokenWithSameExpiration() {
|
||||||
|
JwtAccessToken oldToken = tokenBuilder.build();
|
||||||
|
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true);
|
||||||
|
|
||||||
|
Optional<JwtAccessToken> refreshedTokenResult = refresher.refresh(oldToken);
|
||||||
|
|
||||||
|
assertThat(refreshedTokenResult).isNotEmpty();
|
||||||
|
JwtAccessToken refreshedToken = refreshedTokenResult.get();
|
||||||
|
assertThat(refreshedToken.getExpiration()).isEqualTo(Date.from(NOW.plus(ofMinutes(5))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRefreshTokenWithSameRefreshExpiration() {
|
||||||
|
JwtAccessToken oldToken = tokenBuilder.build();
|
||||||
|
when(refreshStrategy.shouldBeRefreshed(oldToken)).thenReturn(true);
|
||||||
|
|
||||||
|
Optional<JwtAccessToken> refreshedTokenResult = refresher.refresh(oldToken);
|
||||||
|
|
||||||
|
assertThat(refreshedTokenResult).isNotEmpty();
|
||||||
|
JwtAccessToken refreshedToken = refreshedTokenResult.get();
|
||||||
|
assertThat(refreshedToken.getRefreshExpiration()).get().isEqualTo(Date.from(TOKEN_CREATION.plus(ofMinutes(10))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,8 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||||
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,12 +216,6 @@ public class JwtAccessTokenResolverTest {
|
|||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecureKey createSecureKey() {
|
|
||||||
byte[] bytes = new byte[32];
|
|
||||||
random.nextBytes(bytes);
|
|
||||||
return new SecureKey(bytes, System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resolveKey(SecureKey key) {
|
private void resolveKey(SecureKey key) {
|
||||||
when(
|
when(
|
||||||
keyResolver.resolveSigningKey(
|
keyResolver.resolveSigningKey(
|
||||||
@@ -230,7 +226,7 @@ public class JwtAccessTokenResolverTest {
|
|||||||
.thenReturn(
|
.thenReturn(
|
||||||
new SecretKeySpec(
|
new SecretKeySpec(
|
||||||
key.getBytes(),
|
key.getBytes(),
|
||||||
SignatureAlgorithm.HS256.getValue()
|
SignatureAlgorithm.HS256.getJcaName()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static java.time.temporal.ChronoUnit.MINUTES;
|
||||||
|
import static java.time.temporal.ChronoUnit.SECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.HOURS;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static sonia.scm.security.SecureKeyTestUtil.createSecureKey;
|
||||||
|
|
||||||
|
@SubjectAware(
|
||||||
|
username = "user",
|
||||||
|
password = "secret",
|
||||||
|
configuration = "classpath:sonia/scm/repository/shiro.ini"
|
||||||
|
)
|
||||||
|
public class PercentageJwtAccessTokenRefreshStrategyTest {
|
||||||
|
|
||||||
|
private static final Instant TOKEN_CREATION = Instant.now().truncatedTo(SECONDS);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
|
private KeyGenerator keyGenerator = () -> "key";
|
||||||
|
|
||||||
|
private Clock refreshClock = mock(Clock.class);
|
||||||
|
|
||||||
|
private JwtAccessTokenBuilder tokenBuilder;
|
||||||
|
private PercentageJwtAccessTokenRefreshStrategy refreshStrategy;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void initToken() {
|
||||||
|
SecureKeyResolver keyResolver = mock(SecureKeyResolver.class);
|
||||||
|
when(keyResolver.getSecureKey(any())).thenReturn(createSecureKey());
|
||||||
|
|
||||||
|
Clock creationClock = mock(Clock.class);
|
||||||
|
when(creationClock.instant()).thenReturn(TOKEN_CREATION);
|
||||||
|
|
||||||
|
tokenBuilder = new JwtAccessTokenBuilderFactory(keyGenerator, keyResolver, Collections.emptySet(), creationClock).create();
|
||||||
|
tokenBuilder.expiresIn(1, HOURS);
|
||||||
|
tokenBuilder.refreshableFor(1, HOURS);
|
||||||
|
|
||||||
|
refreshStrategy = new PercentageJwtAccessTokenRefreshStrategy(refreshClock, 0.5F);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotRefreshWhenTokenIsYoung() {
|
||||||
|
when(refreshClock.instant()).thenReturn(TOKEN_CREATION.plus(29, MINUTES));
|
||||||
|
assertThat(refreshStrategy.shouldBeRefreshed(tokenBuilder.build())).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRefreshWhenTokenIsOld() {
|
||||||
|
when(refreshClock.instant()).thenReturn(TOKEN_CREATION.plus(31, MINUTES));
|
||||||
|
assertThat(refreshStrategy.shouldBeRefreshed(tokenBuilder.build())).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package sonia.scm.security;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class SecureKeyTestUtil {
|
||||||
|
public static SecureKey createSecureKey() {
|
||||||
|
byte[] bytes = new byte[32];
|
||||||
|
new SecureRandom().nextBytes(bytes);
|
||||||
|
return new SecureKey(bytes, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package sonia.scm.web.security;
|
||||||
|
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.security.AccessTokenCookieIssuer;
|
||||||
|
import sonia.scm.security.AccessTokenResolver;
|
||||||
|
import sonia.scm.security.BearerToken;
|
||||||
|
import sonia.scm.security.JwtAccessToken;
|
||||||
|
import sonia.scm.security.JwtAccessTokenRefresher;
|
||||||
|
import sonia.scm.web.WebTokenGenerator;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static java.util.Collections.singleton;
|
||||||
|
import static java.util.Optional.of;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith({MockitoExtension.class})
|
||||||
|
class TokenRefreshFilterTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Set<WebTokenGenerator> tokenGenerators;
|
||||||
|
@Mock
|
||||||
|
private WebTokenGenerator tokenGenerator;
|
||||||
|
@Mock
|
||||||
|
private JwtAccessTokenRefresher refresher;
|
||||||
|
@Mock
|
||||||
|
private AccessTokenResolver resolver;
|
||||||
|
@Mock
|
||||||
|
private AccessTokenCookieIssuer issuer;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private TokenRefreshFilter filter;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest request;
|
||||||
|
@Mock
|
||||||
|
private HttpServletResponse response;
|
||||||
|
@Mock
|
||||||
|
private FilterChain filterChain;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initGenerators() {
|
||||||
|
when(tokenGenerators.iterator()).thenReturn(singleton(tokenGenerator).iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldContinueChain() throws IOException, ServletException {
|
||||||
|
filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(filterChain).doFilter(request, response);
|
||||||
|
verify(issuer, never()).authenticate(any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotRefreshNonBearerToken() throws IOException, ServletException {
|
||||||
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
||||||
|
when(tokenGenerator.createToken(request)).thenReturn(token);
|
||||||
|
|
||||||
|
filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(issuer, never()).authenticate(any(), any(), any());
|
||||||
|
verify(filterChain).doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotRefreshNonJwtToken() throws IOException, ServletException {
|
||||||
|
BearerToken token = mock(BearerToken.class);
|
||||||
|
JwtAccessToken jwtToken = mock(JwtAccessToken.class);
|
||||||
|
when(tokenGenerator.createToken(request)).thenReturn(token);
|
||||||
|
when(resolver.resolve(token)).thenReturn(jwtToken);
|
||||||
|
|
||||||
|
filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(issuer, never()).authenticate(any(), any(), any());
|
||||||
|
verify(filterChain).doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRefreshIfRefreshable() throws IOException, ServletException {
|
||||||
|
BearerToken token = mock(BearerToken.class);
|
||||||
|
JwtAccessToken jwtToken = mock(JwtAccessToken.class);
|
||||||
|
JwtAccessToken newJwtToken = mock(JwtAccessToken.class);
|
||||||
|
when(tokenGenerator.createToken(request)).thenReturn(token);
|
||||||
|
when(resolver.resolve(token)).thenReturn(jwtToken);
|
||||||
|
when(refresher.refresh(jwtToken)).thenReturn(of(newJwtToken));
|
||||||
|
|
||||||
|
filter.doFilter(request, response, filterChain);
|
||||||
|
|
||||||
|
verify(issuer).authenticate(request, response, newJwtToken);
|
||||||
|
verify(filterChain).doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ dent = secret, creator, heartOfGold, puzzle42
|
|||||||
unpriv = secret
|
unpriv = secret
|
||||||
crato = secret, creator
|
crato = secret, creator
|
||||||
community = secret, oss
|
community = secret, oss
|
||||||
|
user = secret, user
|
||||||
|
|
||||||
[roles]
|
[roles]
|
||||||
admin = *
|
admin = *
|
||||||
@@ -11,3 +12,4 @@ creator = repository:create
|
|||||||
heartOfGold = "repository:read,modify,delete:hof"
|
heartOfGold = "repository:read,modify,delete:hof"
|
||||||
puzzle42 = "repository:read,write:p42"
|
puzzle42 = "repository:read,write:p42"
|
||||||
oss = "repository:pull"
|
oss = "repository:pull"
|
||||||
|
user = *
|
||||||
|
|||||||
Reference in New Issue
Block a user