mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 14:35:45 +01:00
Merged feature/changes_for_ssh_plugin into 2.0.0-m3
This commit is contained in:
@@ -77,15 +77,13 @@ public final class ScmState
|
||||
* @param repositoryTypes available repository types
|
||||
* @param defaultUserType default user type
|
||||
* @param clientConfig client configuration
|
||||
* @param assignedPermission assigned permissions
|
||||
* @param availablePermissions list of available permissions
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public ScmState(String version, User user, Collection<String> groups,
|
||||
String token, Collection<RepositoryType> repositoryTypes, String defaultUserType,
|
||||
ScmClientConfig clientConfig, List<String> assignedPermission,
|
||||
Collection<PermissionDescriptor> availablePermissions)
|
||||
ScmClientConfig clientConfig, Collection<PermissionDescriptor> availablePermissions)
|
||||
{
|
||||
this.version = version;
|
||||
this.user = user;
|
||||
@@ -94,24 +92,11 @@ public final class ScmState
|
||||
this.repositoryTypes = repositoryTypes;
|
||||
this.clientConfig = clientConfig;
|
||||
this.defaultUserType = defaultUserType;
|
||||
this.assignedPermissions = assignedPermission;
|
||||
this.availablePermissions = availablePermissions;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Return a list of assigned permissions.
|
||||
*
|
||||
*
|
||||
* @return list of assigned permissions
|
||||
* @since 1.31
|
||||
*/
|
||||
public List<String> getAssignedPermissions()
|
||||
{
|
||||
return assignedPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of available global permissions.
|
||||
*
|
||||
@@ -225,9 +210,6 @@ public final class ScmState
|
||||
/** authentication token */
|
||||
private String token;
|
||||
|
||||
/** Field description */
|
||||
private List<String> assignedPermissions;
|
||||
|
||||
/**
|
||||
* Avaliable global permission
|
||||
* @since 1.31
|
||||
|
||||
@@ -74,20 +74,17 @@ public final class ScmStateFactory
|
||||
* @param repositoryManger repository manager
|
||||
* @param userManager user manager
|
||||
* @param securitySystem security system
|
||||
* @param authorizationCollector authorization collector
|
||||
*/
|
||||
@Inject
|
||||
public ScmStateFactory(SCMContextProvider contextProvider,
|
||||
ScmConfiguration configuration, RepositoryManager repositoryManger,
|
||||
UserManager userManager, SecuritySystem securitySystem,
|
||||
AuthorizationCollector authorizationCollector)
|
||||
UserManager userManager, SecuritySystem securitySystem)
|
||||
{
|
||||
this.contextProvider = contextProvider;
|
||||
this.configuration = configuration;
|
||||
this.repositoryManger = repositoryManger;
|
||||
this.userManager = userManager;
|
||||
this.securitySystem = securitySystem;
|
||||
this.authorizationCollector = authorizationCollector;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -101,8 +98,7 @@ public final class ScmStateFactory
|
||||
@SuppressWarnings("unchecked")
|
||||
public ScmState createAnonymousState()
|
||||
{
|
||||
return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, null,
|
||||
Collections.EMPTY_LIST, Collections.EMPTY_LIST);
|
||||
return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, null, Collections.EMPTY_LIST);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,15 +137,11 @@ public final class ScmStateFactory
|
||||
ap = securitySystem.getAvailablePermissions();
|
||||
}
|
||||
|
||||
List<String> permissions =
|
||||
ImmutableList.copyOf(
|
||||
authorizationCollector.collect().getStringPermissions());
|
||||
|
||||
return createState(user, groups.getCollection(), token, permissions, ap);
|
||||
return createState(user, groups.getCollection(), token, ap);
|
||||
}
|
||||
|
||||
private ScmState createState(User user, Collection<String> groups,
|
||||
String token, List<String> assignedPermissions,
|
||||
String token,
|
||||
Collection<PermissionDescriptor> availablePermissions)
|
||||
{
|
||||
User u = user.clone();
|
||||
@@ -159,15 +151,11 @@ public final class ScmStateFactory
|
||||
|
||||
return new ScmState(contextProvider.getVersion(), u, groups, token,
|
||||
repositoryManger.getConfiguredTypes(), userManager.getDefaultType(),
|
||||
new ScmClientConfig(configuration), assignedPermissions,
|
||||
availablePermissions);
|
||||
new ScmClientConfig(configuration), availablePermissions);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** authorization collector */
|
||||
private final AuthorizationCollector authorizationCollector;
|
||||
|
||||
/** configuration */
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
|
||||
@@ -3,10 +3,29 @@ package sonia.scm.repository.api;
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
import sonia.scm.repository.Repository;
|
||||
|
||||
/**
|
||||
* Provider for scm native protocols.
|
||||
*
|
||||
* @param <T> type of protocol
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ExtensionPoint(multi = true)
|
||||
public interface ScmProtocolProvider<T extends ScmProtocol> {
|
||||
|
||||
/**
|
||||
* Returns type of repository (e.g.: git, svn, hg, etc.)
|
||||
*
|
||||
* @return name of type
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* Returns protocol for the given repository.
|
||||
*
|
||||
* @param repository repository
|
||||
*
|
||||
* @return protocol for repository
|
||||
*/
|
||||
T get(Repository repository);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ package sonia.scm.security;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import sonia.scm.plugin.ExtensionPoint;
|
||||
|
||||
/**
|
||||
@@ -42,15 +43,16 @@ import sonia.scm.plugin.ExtensionPoint;
|
||||
* @author Sebastian Sdorra
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ExtensionPoint(multi = false)
|
||||
@ExtensionPoint
|
||||
public interface AuthorizationCollector
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns {@link AuthorizationInfo} for the authenticated user.
|
||||
*
|
||||
* @param principalCollection collected principals
|
||||
*
|
||||
* @return {@link AuthorizationInfo} for authenticated user
|
||||
*/
|
||||
public AuthorizationInfo collect();
|
||||
AuthorizationInfo collect(PrincipalCollection principalCollection);
|
||||
}
|
||||
|
||||
57
scm-plugins/scm-git-plugin/src/main/js/CloneInformation.js
Normal file
57
scm-plugins/scm-git-plugin/src/main/js/CloneInformation.js
Normal file
@@ -0,0 +1,57 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
url: string,
|
||||
repository: Repository,
|
||||
|
||||
// context props
|
||||
t: (string) => string
|
||||
};
|
||||
|
||||
class CloneInformation extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { url, repository, t } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>{t("scm-git-plugin.information.clone")}</h4>
|
||||
<pre>
|
||||
<code>git clone {url}</code>
|
||||
</pre>
|
||||
<h4>{t("scm-git-plugin.information.create")}</h4>
|
||||
<pre>
|
||||
<code>
|
||||
git init {repository.name}
|
||||
<br />
|
||||
echo "# {repository.name}" > README.md
|
||||
<br />
|
||||
git add README.md
|
||||
<br />
|
||||
git commit -m "added readme"
|
||||
<br />
|
||||
git remote add origin {url}
|
||||
<br />
|
||||
git push -u origin master
|
||||
<br />
|
||||
</code>
|
||||
</pre>
|
||||
<h4>{t("scm-git-plugin.information.replace")}</h4>
|
||||
<pre>
|
||||
<code>
|
||||
git remote add origin {url}
|
||||
<br />
|
||||
git push -u origin master
|
||||
<br />
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate("plugins")(CloneInformation);
|
||||
@@ -1,59 +1,108 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import { repositories } from "@scm-manager/ui-components";
|
||||
import { ButtonGroup, Button } from "@scm-manager/ui-components";
|
||||
import type { Repository } from "@scm-manager/ui-types";
|
||||
import { translate } from "react-i18next";
|
||||
import CloneInformation from "./CloneInformation";
|
||||
import type { Link } from "@scm-manager/ui-types";
|
||||
import injectSheets from "react-jss";
|
||||
|
||||
const styles = {
|
||||
protocols: {
|
||||
position: "relative"
|
||||
},
|
||||
switcher: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
t: string => string
|
||||
|
||||
// context props
|
||||
classes: Object
|
||||
}
|
||||
|
||||
class ProtocolInformation extends React.Component<Props> {
|
||||
type State = {
|
||||
selected?: Link
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repository, t } = this.props;
|
||||
const href = repositories.getProtocolLinkByType(repository, "http");
|
||||
if (!href) {
|
||||
return null;
|
||||
function selectHttpOrFirst(repository: Repository) {
|
||||
const protocols = repository._links["protocol"] || [];
|
||||
|
||||
for (let protocol of protocols) {
|
||||
if (protocol.name === "http") {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
|
||||
if (protocols.length > 0) {
|
||||
return protocols[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
class ProtocolInformation extends React.Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selected: selectHttpOrFirst(props.repository)
|
||||
};
|
||||
}
|
||||
|
||||
selectProtocol = (protocol: Link) => {
|
||||
this.setState({
|
||||
selected: protocol
|
||||
});
|
||||
};
|
||||
|
||||
renderProtocolButton = (protocol: Link) => {
|
||||
const name = protocol.name || "unknown";
|
||||
|
||||
let color = null;
|
||||
|
||||
const { selected } = this.state;
|
||||
if ( selected && protocol.name === selected.name ) {
|
||||
color = "link is-selected";
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>{t("scm-git-plugin.information.clone")}</h4>
|
||||
<pre>
|
||||
<code>git clone {href}</code>
|
||||
</pre>
|
||||
<h4>{t("scm-git-plugin.information.create")}</h4>
|
||||
<pre>
|
||||
<code>
|
||||
git init {repository.name}
|
||||
<br />
|
||||
echo "# {repository.name}" > README.md
|
||||
<br />
|
||||
git add README.md
|
||||
<br />
|
||||
git commit -m "added readme"
|
||||
<br />
|
||||
git remote add origin {href}
|
||||
<br />
|
||||
git push -u origin master
|
||||
<br />
|
||||
</code>
|
||||
</pre>
|
||||
<h4>{t("scm-git-plugin.information.replace")}</h4>
|
||||
<pre>
|
||||
<code>
|
||||
git remote add origin {href}
|
||||
<br />
|
||||
git push -u origin master
|
||||
<br />
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<Button color={ color } action={() => this.selectProtocol(protocol)}>
|
||||
{name.toUpperCase()}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { repository, classes } = this.props;
|
||||
|
||||
const protocols = repository._links["protocol"];
|
||||
if (!protocols || protocols.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (protocols.length === 1) {
|
||||
return <CloneInformation url={protocols[0].href} repository={repository} />;
|
||||
}
|
||||
|
||||
const { selected } = this.state;
|
||||
let cloneInformation = null;
|
||||
if (selected) {
|
||||
cloneInformation = <CloneInformation repository={repository} url={selected.href} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.protocols}>
|
||||
<ButtonGroup className={classes.switcher}>
|
||||
{protocols.map(this.renderProtocolButton)}
|
||||
</ButtonGroup>
|
||||
{ cloneInformation }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate("plugins")(ProtocolInformation);
|
||||
export default injectSheets(styles)(ProtocolInformation);
|
||||
|
||||
@@ -73,6 +73,11 @@ class Profile extends React.Component<Props, State> {
|
||||
path={`${url}/settings/password`}
|
||||
render={() => <ChangeUserPassword me={me} />}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="profile.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
|
||||
@@ -105,6 +105,11 @@ class SingleUser extends React.Component<Props> {
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<ExtensionPoint
|
||||
name="user.route"
|
||||
props={extensionProps}
|
||||
renderAll={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="column">
|
||||
<Navigation>
|
||||
|
||||
@@ -36,6 +36,7 @@ package sonia.scm.security;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -118,8 +119,8 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationInfo collect()
|
||||
@VisibleForTesting
|
||||
AuthorizationInfo collect()
|
||||
{
|
||||
AuthorizationInfo authorizationInfo;
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
@@ -143,6 +144,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationInfo collect(PrincipalCollection principals)
|
||||
{
|
||||
Preconditions.checkNotNull(principals, "principals parameter is required");
|
||||
|
||||
@@ -42,9 +42,11 @@ import org.apache.shiro.authc.UsernamePasswordToken;
|
||||
import org.apache.shiro.authc.credential.PasswordMatcher;
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
import org.apache.shiro.realm.AuthorizingRealm;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import sonia.scm.group.GroupNames;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
@@ -56,6 +58,8 @@ import javax.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Default authorizing realm.
|
||||
*
|
||||
@@ -85,14 +89,13 @@ public class DefaultRealm extends AuthorizingRealm
|
||||
*
|
||||
*
|
||||
* @param service
|
||||
* @param collector
|
||||
* @param authorizationCollectors
|
||||
* @param helperFactory
|
||||
*/
|
||||
@Inject
|
||||
public DefaultRealm(PasswordService service,
|
||||
DefaultAuthorizationCollector collector, DAORealmHelperFactory helperFactory)
|
||||
public DefaultRealm(PasswordService service, Set<AuthorizationCollector> authorizationCollectors, DAORealmHelperFactory helperFactory)
|
||||
{
|
||||
this.collector = collector;
|
||||
this.authorizationCollectors = authorizationCollectors;
|
||||
this.helper = helperFactory.create(REALM);
|
||||
|
||||
PasswordMatcher matcher = new PasswordMatcher();
|
||||
@@ -133,8 +136,7 @@ public class DefaultRealm extends AuthorizingRealm
|
||||
@Override
|
||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
|
||||
{
|
||||
AuthorizationInfo info = collector.collect(principals);
|
||||
|
||||
AuthorizationInfo info = collectors(principals);
|
||||
Scope scope = principals.oneByType(Scope.class);
|
||||
if (scope != null && ! scope.isEmpty()) {
|
||||
LOG.trace("filter permissions by scope {}", scope);
|
||||
@@ -144,13 +146,36 @@ public class DefaultRealm extends AuthorizingRealm
|
||||
}
|
||||
return filtered;
|
||||
} else if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("principal does not contain scope informations, returning all permissions");
|
||||
LOG.trace("principal does not contain scope information, returning all permissions");
|
||||
log(principals, info, null);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private AuthorizationInfo collectors(PrincipalCollection principals) {
|
||||
SimpleAuthorizationInfo merged = new SimpleAuthorizationInfo();
|
||||
for (AuthorizationCollector collector : authorizationCollectors) {
|
||||
AuthorizationInfo authorizationInfo = collector.collect(principals);
|
||||
merge(merged, authorizationInfo);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
private void merge(SimpleAuthorizationInfo merged, AuthorizationInfo authorizationInfo) {
|
||||
if (authorizationInfo != null) {
|
||||
if (authorizationInfo.getRoles() != null) {
|
||||
merged.addRoles(authorizationInfo.getRoles());
|
||||
}
|
||||
if (authorizationInfo.getObjectPermissions() != null) {
|
||||
merged.addObjectPermissions(authorizationInfo.getObjectPermissions());
|
||||
}
|
||||
if (authorizationInfo.getStringPermissions() != null) {
|
||||
merged.addStringPermissions(authorizationInfo.getStringPermissions());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void log( PrincipalCollection collection, AuthorizationInfo original, AuthorizationInfo filtered ) {
|
||||
StringBuilder buffer = new StringBuilder("authorization summary: ");
|
||||
|
||||
@@ -190,8 +215,8 @@ public class DefaultRealm extends AuthorizingRealm
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** default authorization collector */
|
||||
private final DefaultAuthorizationCollector collector;
|
||||
/** set of authorization collector */
|
||||
private final Set<AuthorizationCollector> authorizationCollectors;
|
||||
|
||||
/** realm helper */
|
||||
private final DAORealmHelper helper;
|
||||
|
||||
@@ -205,7 +205,7 @@ private long calculateAverage(List<Long> times) {
|
||||
|
||||
@Override
|
||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
|
||||
return authzCollector.collect();
|
||||
return authzCollector.collect(principals);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,7 +71,11 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.authz.Permission;
|
||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
@@ -132,6 +136,36 @@ public class DefaultRealmTest
|
||||
assertThat(realmsAutz.getStringPermissions(), Matchers.contains("repository:*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAuthorizationInfoWithMultipleAuthorizationCollectors(){
|
||||
SimplePrincipalCollection col = new SimplePrincipalCollection();
|
||||
col.add(Scope.empty(), DefaultRealm.REALM);
|
||||
|
||||
SimpleAuthorizationInfo collectedFromDefault = new SimpleAuthorizationInfo();
|
||||
collectedFromDefault.addStringPermission("repository:*");
|
||||
when(collector.collect(col)).thenReturn(collectedFromDefault);
|
||||
|
||||
SimpleAuthorizationInfo collectedFromSecond = new SimpleAuthorizationInfo();
|
||||
collectedFromSecond.addStringPermission("user:*");
|
||||
collectedFromSecond.addRole("awesome");
|
||||
|
||||
AuthorizationCollector secondCollector = principalCollection -> collectedFromSecond;
|
||||
authorizationCollectors.add(secondCollector);
|
||||
|
||||
SimpleAuthorizationInfo collectedFromThird = new SimpleAuthorizationInfo();
|
||||
Permission permission = p -> false;
|
||||
collectedFromThird.addObjectPermission(permission);
|
||||
collectedFromThird.addRole("awesome");
|
||||
|
||||
AuthorizationCollector thirdCollector = principalCollection -> collectedFromThird;
|
||||
authorizationCollectors.add(thirdCollector);
|
||||
|
||||
AuthorizationInfo realmsAuthz = realm.doGetAuthorizationInfo(col);
|
||||
assertThat(realmsAuthz.getObjectPermissions(), contains(permission));
|
||||
assertThat(realmsAuthz.getStringPermissions(), containsInAnyOrder("repository:*", "user:*"));
|
||||
assertThat(realmsAuthz.getRoles(), Matchers.contains("awesome"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link DefaultRealm#doGetAuthorizationInfo(PrincipalCollection)} with empty scope.
|
||||
*/
|
||||
@@ -284,7 +318,11 @@ public class DefaultRealmTest
|
||||
// use a small number of iterations for faster test execution
|
||||
hashService.setHashIterations(512);
|
||||
service.setHashService(hashService);
|
||||
realm = new DefaultRealm(service, collector, helperFactory);
|
||||
|
||||
authorizationCollectors = new HashSet<>();
|
||||
authorizationCollectors.add(collector);
|
||||
|
||||
realm = new DefaultRealm(service, authorizationCollectors, helperFactory);
|
||||
|
||||
// set permission resolver
|
||||
realm.setPermissionResolver(new WildcardPermissionResolver());
|
||||
@@ -358,6 +396,8 @@ public class DefaultRealmTest
|
||||
@Mock
|
||||
private DefaultAuthorizationCollector collector;
|
||||
|
||||
private Set<AuthorizationCollector> authorizationCollectors;
|
||||
|
||||
@Mock
|
||||
private LoginAttemptHandler loginAttemptHandler;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user