Merge with develop

This commit is contained in:
Sebastian Sdorra
2020-11-04 08:22:41 +01:00
125 changed files with 2108 additions and 851 deletions

View File

@@ -189,6 +189,14 @@ public class ScmConfiguration implements Configuration {
@XmlElement(name = "xsrf-protection")
private boolean enabledXsrfProtection = true;
/**
* Enables user converter.
*
* @since 2.9.0
*/
@XmlElement(name = "user-converter")
private boolean enabledUserConverter = false;
@XmlElement(name = "namespace-strategy")
private String namespaceStrategy = "UsernameNamespaceStrategy";
@@ -238,6 +246,7 @@ public class ScmConfiguration implements Configuration {
this.loginInfoUrl = other.loginInfoUrl;
this.releaseFeedUrl = other.releaseFeedUrl;
this.mailDomainName = other.mailDomainName;
this.enabledUserConverter = other.enabledUserConverter;
}
/**
@@ -387,6 +396,17 @@ public class ScmConfiguration implements Configuration {
return enabledXsrfProtection;
}
/**
* Returns {@code true} if the user converter is enabled.
*
* @return {@code true} if the user converter is enabled
* The user converter automatically converts an internal user to external on their first login using an external system like ldap
* @since 2.9.0
*/
public boolean isEnabledUserConverter() {
return enabledUserConverter;
}
public boolean isEnableProxy() {
return enableProxy;
}
@@ -554,6 +574,16 @@ public class ScmConfiguration implements Configuration {
this.enabledXsrfProtection = enabledXsrfProtection;
}
/**
* Set {@code true} to enable user converter.
*
* @param enabledUserConverter {@code true} to enable user converter
* @since 2.9.0
*/
public void setEnabledUserConverter(boolean enabledUserConverter) {
this.enabledUserConverter = enabledUserConverter;
}
public void setNamespaceStrategy(String namespaceStrategy) {
this.namespaceStrategy = namespaceStrategy;
}

View File

@@ -21,18 +21,20 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import sonia.scm.Validateable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.regex.Pattern;
//~--- JDK imports ------------------------------------------------------------
@@ -44,9 +46,14 @@ import java.io.Serializable;
*/
@XmlRootElement(name = "branch")
@XmlAccessorType(XmlAccessType.FIELD)
public final class Branch implements Serializable
public final class Branch implements Serializable, Validateable
{
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/.";
public static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?";
public static final Pattern VALID_BRANCH_NAME_PATTERN = Pattern.compile(VALID_BRANCH_NAMES);
/** Field description */
private static final long serialVersionUID = -4602244691711222413L;
@@ -83,6 +90,11 @@ public final class Branch implements Serializable
//~--- methods --------------------------------------------------------------
@Override
public boolean isValid() {
return VALID_BRANCH_NAME_PATTERN.matcher(name).matches();
}
/**
* {@inheritDoc}
*

View File

@@ -34,6 +34,7 @@ import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.spi.RepositoryServiceProvider;
import sonia.scm.repository.work.WorkdirProvider;
import sonia.scm.security.Authentications;
import sonia.scm.user.EMail;
import javax.annotation.Nullable;
@@ -453,7 +454,8 @@ public final class RepositoryService implements Closeable {
public Stream<ScmProtocol> getSupportedProtocols() {
return protocolProviders.stream()
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
.map(this::createProviderInstanceForRepository);
.map(this::createProviderInstanceForRepository)
.filter(protocol -> !Authentications.isAuthenticatedSubjectAnonymous() || protocol.isAnonymousEnabled());
}
@SuppressWarnings({"rawtypes", "java:S3740"})

View File

@@ -40,4 +40,11 @@ public interface ScmProtocol {
* The URL to access the repository providing this protocol.
*/
String getUrl();
/**
* Whether the protocol can be used as an anonymous user.
*/
default boolean isAnonymousEnabled() {
return true;
}
}

View File

@@ -21,10 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.security;
import com.google.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.subject.SimplePrincipalCollection;
@@ -33,10 +34,14 @@ import sonia.scm.NotFoundException;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.plugin.Extension;
import sonia.scm.user.ExternalUserConverter;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.web.security.AdministrationContext;
import java.util.Collections;
import java.util.Set;
/**
* Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated
* users with the local database.
@@ -44,34 +49,49 @@ import sonia.scm.web.security.AdministrationContext;
* @author Sebastian Sdorra
* @since 2.0.0
*/
@Slf4j
@Extension
public final class SyncingRealmHelper {
private final AdministrationContext ctx;
private final UserManager userManager;
private final GroupManager groupManager;
private final Set<ExternalUserConverter> externalUserConverters;
/**
* Constructs a new SyncingRealmHelper.
*
* @param ctx administration context
* @param userManager user manager
* @param groupManager group manager
* @param ctx administration context
* @param userManager user manager
* @param groupManager group manager
* @param externalUserConverters global scm configuration
*/
@Inject
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager) {
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager, Set<ExternalUserConverter> externalUserConverters) {
this.ctx = ctx;
this.userManager = userManager;
this.groupManager = groupManager;
this.externalUserConverters = externalUserConverters;
}
/**
* Constructs a new SyncingRealmHelper.
*
* @param ctx administration context
* @param userManager user manager
* @param groupManager group manager
* @deprecated Use {@link #SyncingRealmHelper(AdministrationContext, UserManager, GroupManager, Set)} instead.
*/
@Deprecated
public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager) {
this(ctx, userManager, groupManager, Collections.emptySet());
}
/**
* Create {@link AuthenticationInfo} from user and groups.
*
*
* @param realm name of the realm
* @param user authenticated user
*
* @param user authenticated user
* @return authentication info
*/
public AuthenticationInfo createAuthenticationInfo(String realm, User user) {
@@ -91,17 +111,9 @@ public final class SyncingRealmHelper {
public void store(final Group group) {
ctx.runAsAdmin(() -> {
if (groupManager.get(group.getId()) != null) {
try {
groupManager.modify(group);
} catch (NotFoundException e) {
throw new IllegalStateException("got NotFoundException though group " + group.getName() + " could be loaded", e);
}
modifyGroup(group);
} else {
try {
groupManager.create(group);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though group " + group.getName() + " could not be loaded", e);
}
createNewGroup(group);
}
});
}
@@ -114,19 +126,54 @@ public final class SyncingRealmHelper {
public void store(final User user) {
ctx.runAsAdmin(() -> {
if (userManager.contains(user.getName())) {
try {
userManager.modify(user);
} catch (NotFoundException e) {
throw new IllegalStateException("got NotFoundException though user " + user.getName() + " could be loaded", e);
}
modifyUser(user);
} else {
try {
userManager.create(user);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though user " + user.getName() + " could not be loaded", e);
}
createNewUser(user);
}
});
}
private void createNewUser(User user) {
try {
User clone = user.clone();
// New user created by syncing realm helper is always external
clone.setExternal(true);
userManager.create(clone);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though user " + user.getName() + " could not be loaded", e);
}
}
private void modifyUser(User user) {
User clone = user.clone();
if (!externalUserConverters.isEmpty()) {
log.debug("execute available user converters");
for (ExternalUserConverter converter : externalUserConverters) {
clone = converter.convert(clone);
}
}
try {
userManager.modify(clone);
} catch (NotFoundException e) {
throw new IllegalStateException("got NotFoundException though user " + clone.getName() + " could be loaded", e);
}
}
private void createNewGroup(Group group) {
try {
groupManager.create(group);
} catch (AlreadyExistsException e) {
throw new IllegalStateException("got AlreadyExistsException though group " + group.getName() + " could not be loaded", e);
}
}
private void modifyGroup(Group group) {
try {
groupManager.modify(group);
} catch (NotFoundException e) {
throw new IllegalStateException("got NotFoundException though group " + group.getName() + " could be loaded", e);
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.user;
import sonia.scm.plugin.ExtensionPoint;
/**
* The external user converter can be used to modify users
* which are provided by external systems before creation in SCM-Manager.
* The implementations will be called in the {@link sonia.scm.security.SyncingRealmHelper}
* @since 2.9.0
*/
@ExtensionPoint
public interface ExternalUserConverter {
/**
* Returns the converted user.
* @return converted user
*/
User convert(User user);
}

View File

@@ -24,12 +24,14 @@
package sonia.scm.user;
//~--- non-JDK imports --------------------------------------------------------
import com.github.sdorra.ssp.PermissionObject;
import com.github.sdorra.ssp.StaticPermissions;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import sonia.scm.BasicPropertiesAware;
import sonia.scm.ModelObject;
import sonia.scm.ReducedModelObject;
@@ -41,12 +43,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.security.Principal;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
@StaticPermissions(
value = "user",
globalPermissions = {"create", "list", "autocomplete"},
@@ -55,57 +51,42 @@ import java.security.Principal;
)
@XmlRootElement(name = "users")
@XmlAccessorType(XmlAccessType.FIELD)
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject
{
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User extends BasicPropertiesAware implements Principal, ModelObject, PermissionObject, ReducedModelObject {
/** Field description */
private static final long serialVersionUID = -3089541936726329663L;
//~--- constructors ---------------------------------------------------------
private boolean active = true;
private boolean external;
private Long creationDate;
private String displayName;
private Long lastModified;
private String mail;
private String name;
private String password;
/**
* Constructs ...
*
* The user type is replaced by {@link #external} flag
* @deprecated Use {@link #external} instead.
*/
public User() {}
@Deprecated
private String type;
/**
* Constructs ...
*
*
* @param name
*/
public User(String name)
{
public User(String name) {
this.name = name;
this.displayName = name;
}
/**
* Constructs ...
*
*
* @param name
* @param displayName
* @param mail
*/
public User(String name, String displayName, String mail)
{
public User(String name, String displayName, String mail) {
this.name = name;
this.displayName = displayName;
this.mail = mail;
}
/**
* Constructs ...
*
*
* @param name
* @param displayName
* @param mail
*/
public User(String name, String displayName, String mail, String password, String type, boolean active)
{
public User(String name, String displayName, String mail, String password, String type, boolean active) {
this.name = name;
this.displayName = displayName;
this.mail = mail;
@@ -114,90 +95,57 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
this.active = active;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*
*/
@Override
public User clone()
{
User user = null;
public User clone() {
User user;
try
{
try {
user = (User) super.clone();
}
catch (CloneNotSupportedException ex)
{
} catch (CloneNotSupportedException ex) {
throw new RuntimeException(ex);
}
return user;
}
/**
* Method description
*
*
* @param user
*
* @return
*/
public boolean copyProperties(User user)
{
public boolean copyProperties(User user) {
return copyProperties(user, true);
}
/**
* Method description
*
*
* @param user
* @param copyPassword
*
* @return
*/
public boolean copyProperties(User user, boolean copyPassword)
{
public boolean copyProperties(User user, boolean copyPassword) {
boolean result = false;
if (user.isActive() != active)
{
if (user.isActive() != active) {
result = true;
user.setActive(active);
}
if (Util.isNotEquals(user.getDisplayName(), displayName))
{
if (user.isExternal() != external) {
result = true;
user.setExternal(external);
}
if (Util.isNotEquals(user.getDisplayName(), displayName)) {
result = true;
user.setDisplayName(displayName);
}
if (Util.isNotEquals(user.getMail(), mail))
{
if (Util.isNotEquals(user.getMail(), mail)) {
result = true;
user.setMail(mail);
}
if (Util.isNotEquals(user.getName(), name))
{
if (Util.isNotEquals(user.getName(), name)) {
result = true;
user.setName(name);
}
if (copyPassword && Util.isNotEquals(user.getPassword(), password))
{
if (copyPassword && Util.isNotEquals(user.getPassword(), password)) {
result = true;
user.setPassword(password);
}
if (Util.isNotEquals(user.getType(), type))
{
if (Util.isNotEquals(user.getType(), type)) {
result = true;
user.setType(type);
}
@@ -205,316 +153,65 @@ public class User extends BasicPropertiesAware implements Principal, ModelObject
return result;
}
/**
* {@inheritDoc}
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass())
{
if (getClass() != obj.getClass()) {
return false;
}
final User other = (User) obj;
return Objects.equal(name, other.name)
&& Objects.equal(displayName, other.displayName)
&& Objects.equal(mail, other.mail)
&& Objects.equal(type, other.type)
&& Objects.equal(active, other.active)
&& Objects.equal(password, other.password)
&& Objects.equal(creationDate, other.creationDate)
&& Objects.equal(lastModified, other.lastModified)
&& Objects.equal(properties, other.properties);
&& Objects.equal(displayName, other.displayName)
&& Objects.equal(mail, other.mail)
&& Objects.equal(external, other.external)
&& Objects.equal(active, other.active)
&& Objects.equal(password, other.password)
&& Objects.equal(creationDate, other.creationDate)
&& Objects.equal(lastModified, other.lastModified)
&& Objects.equal(properties, other.properties);
}
/**
* {@inheritDoc}
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(name, displayName, mail, type, password,
active, creationDate, lastModified, properties);
public int hashCode() {
return Objects.hashCode(name, displayName, mail, password,
active, external, creationDate, lastModified, properties);
}
/**
* {@inheritDoc}
*
*
* @return
*/
@Override
public String toString()
{
public String toString() {
String pwd = (password != null)
? "(is set)"
: "(not set)";
? "(is set)"
: "(not set)";
//J-
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("displayName",displayName)
.add("mail", mail)
.add("password", pwd)
.add("type", type)
.add("active", active)
.add("creationDate", creationDate)
.add("lastModified", lastModified)
.add("properties", properties)
.toString();
.add("name", name)
.add("displayName", displayName)
.add("mail", mail)
.add("password", pwd)
.add("type", type)
.add("active", active)
.add("external", external)
.add("creationDate", creationDate)
.add("lastModified", lastModified)
.add("properties", properties)
.toString();
//J+
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Long getCreationDate()
{
return creationDate;
}
/**
* Method description
*
*
* @return
*/
public String getDisplayName()
{
return displayName;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getId()
{
return name;
}
/**
* Method description
*
*
* @return
*/
@Override
public Long getLastModified()
{
return lastModified;
}
/**
* Method description
*
*
* @return
*/
public String getMail()
{
return mail;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getName()
{
return name;
}
/**
* Method description
*
*
* @return
*/
public String getPassword()
{
return password;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getType()
{
return type;
}
/**
* Returns false if the user is deactivated.
*
*
* @return false if the user is deactivated
* @since 1.16
*/
public boolean isActive()
{
return active;
}
/**
* Method description
*
*
* @return
*/
@Override
public boolean isValid()
{
public boolean isValid() {
return ValidationUtil.isNameValid(name) && Util.isNotEmpty(displayName)
&& Util.isNotEmpty(type)
&& ((Util.isEmpty(mail)) || ValidationUtil.isMailAddressValid(mail));
&& ((Util.isEmpty(mail)) || ValidationUtil.isMailAddressValid(mail));
}
//~--- set methods ----------------------------------------------------------
/**
* Activate or deactive this user.
*
*
* @param active false to deactivate the user.
* @since 1.6
*/
public void setActive(boolean active)
{
this.active = active;
@Override
public String getId() {
return name;
}
/**
* Method description
*
*
* @param creationDate
*/
public void setCreationDate(Long creationDate)
{
this.creationDate = creationDate;
}
/**
* Method description
*
*
* @param displayName
*/
public void setDisplayName(String displayName)
{
this.displayName = displayName;
}
/**
* Method description
*
*
* @param lastModified
*/
public void setLastModified(Long lastModified)
{
this.lastModified = lastModified;
}
/**
* Method description
*
*
* @param mail
*/
public void setMail(String mail)
{
this.mail = mail;
}
/**
* Method description
*
*
*
* @param name
*/
public void setName(String name)
{
this.name = name;
}
/**
* Method description
*
*
* @param password
*/
public void setPassword(String password)
{
this.password = password;
}
/**
* Method description
*
*
* @param type
*/
public void setType(String type)
{
this.type = type;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private boolean active = true;
/** Field description */
private Long creationDate;
/** Field description */
private String displayName;
/** Field description */
private Long lastModified;
/** Field description */
private String mail;
/** Field description */
private String name;
/** Field description */
private String password;
/** Field description */
private String type;
}

View File

@@ -21,23 +21,19 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Charsets;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static com.google.common.base.Preconditions.checkNotNull;
//~--- JDK imports ------------------------------------------------------------
/**
* The software agent that is acting on behalf of a user. The user agent
* The software agent that is acting on behalf of a user. The user agent
* represents a browser or one of the repository client (svn, git or hg).
*
* @author Sebastian Sdorra <s.sdorra@gmail.com>
@@ -49,17 +45,16 @@ public final class UserAgent
/**
* Constructs a new user agent
*
*
* @param name
* @param browser
* @param name
* @param basicAuthenticationCharset
* @param browser
*/
private UserAgent(String name, boolean browser,
Charset basicAuthenticationCharset)
private UserAgent(String name, Charset basicAuthenticationCharset, boolean browser, boolean scmClient)
{
this.name = checkNotNull(name);
this.browser = browser;
this.basicAuthenticationCharset = checkNotNull(basicAuthenticationCharset);
this.browser = browser;
this.scmClient = scmClient;
}
//~--- methods --------------------------------------------------------------
@@ -71,8 +66,30 @@ public final class UserAgent
* @param name name of the UserAgent
*
* @return builder for UserAgent
*
* @deprecated Use {@link #browser(String)}, {@link #scmClient(String)} or {@link #other(String)} instead
*/
@Deprecated
public static Builder builder(String name)
{
return other(name);
}
public static Builder browser(String name)
{
final Builder builder = new Builder(name);
builder.browser = true;
return builder;
}
public static Builder scmClient(String name)
{
final Builder builder = new Builder(name);
builder.scmClient = true;
return builder;
}
public static Builder other(String name)
{
return new Builder(name);
}
@@ -97,7 +114,7 @@ public final class UserAgent
return Objects.equal(name, other.name)
&& Objects.equal(browser, other.browser)
&& Objects.equal(basicAuthenticationCharset, basicAuthenticationCharset);
&& Objects.equal(basicAuthenticationCharset, other.basicAuthenticationCharset);
}
/**
@@ -127,7 +144,7 @@ public final class UserAgent
//~--- get methods ----------------------------------------------------------
/**
* Returns the {@link Charset}, which is used to decode the basic
* Returns the {@link Charset}, which is used to decode the basic
* authentication header.
*
* @return {@link Charset} for basic authentication
@@ -152,13 +169,23 @@ public final class UserAgent
* Returns {@code true} if UserAgent is a browser.
*
*
* @return {@code true} if UserAgent is a browser
* @return {@code true} if UserAgent is a browser
*/
public boolean isBrowser()
{
return browser;
}
/**
* Returns {@code true} if UserAgent is an scm client (e.g. git, svn or hg).
*
*
* @return {@code true} if UserAgent is an scm client
*/
public boolean isScmClient() {
return scmClient;
}
//~--- inner classes --------------------------------------------------------
/**
@@ -204,7 +231,10 @@ public final class UserAgent
* @param browser {@code true} for a browser
*
* @return {@code this}
*
* @deprecated Use {@link #browser(String)} instead
*/
@Deprecated
public Builder browser(boolean browser)
{
this.browser = browser;
@@ -215,12 +245,11 @@ public final class UserAgent
/**
* Builds the {@link UserAgent}.
*
*
* @return new {@link UserAgent}
*/
public UserAgent build()
{
return new UserAgent(name, browser, basicAuthenticationCharset);
return new UserAgent(name, basicAuthenticationCharset, browser, scmClient);
}
//~--- fields -------------------------------------------------------------
@@ -229,10 +258,13 @@ public final class UserAgent
private final String name;
/** indicator for browsers */
private boolean browser = true;
private boolean browser = false;
/** indicator for browsers */
private boolean scmClient = false;
/** basic authentication charset */
private Charset basicAuthenticationCharset = Charsets.ISO_8859_1;
private Charset basicAuthenticationCharset = StandardCharsets.ISO_8859_1;
}
@@ -244,6 +276,9 @@ public final class UserAgent
/** indicator for browsers */
private final boolean browser;
/** indicator for scm clients (e.g. git, hg, svn) */
private final boolean scmClient;
/** name of UserAgent */
private final String name;
}

View File

@@ -62,7 +62,7 @@ public final class UserAgentParser
/** unknown UserAgent */
@VisibleForTesting
static final UserAgent UNKNOWN = UserAgent.builder("UNKNOWN").build();
static final UserAgent UNKNOWN = UserAgent.other("UNKNOWN").build();
/** logger */
private static final Logger logger =

View File

@@ -24,7 +24,15 @@
package sonia.scm.repository.api;
import org.junit.Test;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ThreadContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.Repository;
import sonia.scm.repository.spi.HttpScmProtocol;
@@ -42,16 +50,32 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.util.IterableUtil.sizeOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class RepositoryServiceTest {
@ExtendWith(MockitoExtension.class)
class RepositoryServiceTest {
private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class);
private final Repository repository = new Repository("", "git", "space", "repo");
private final EMail eMail = new EMail(new ScmConfiguration());
@Mock
private Subject subject;
@BeforeEach
void bindSubject() {
ThreadContext.bind(subject);
}
@AfterEach
void unbindSubject() {
ThreadContext.unbindSubject();
}
@Test
public void shouldReturnMatchingProtocolsFromProvider() {
void shouldReturnMatchingProtocolsFromProvider() {
when(subject.getPrincipal()).thenReturn("Hitchhiker");
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
@@ -59,7 +83,17 @@ public class RepositoryServiceTest {
}
@Test
public void shouldFindKnownProtocol() {
void shouldFilterOutNonAnonymousEnabledProtocolsForAnonymousUser() {
when(subject.getPrincipal()).thenReturn(SCMContext.USER_ANONYMOUS);
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Stream.of(new DummyScmProtocolProvider(), new DummyScmProtocolProvider(false)).collect(Collectors.toSet()), null, eMail);
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1);
}
@Test
void shouldFindKnownProtocol() {
when(subject.getPrincipal()).thenReturn("Hitchhiker");
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
@@ -68,23 +102,44 @@ public class RepositoryServiceTest {
}
@Test
public void shouldFailForUnknownProtocol() {
void shouldFailForUnknownProtocol() {
when(subject.getPrincipal()).thenReturn("Hitchhiker");
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
assertThrows(IllegalArgumentException.class, () -> repositoryService.getProtocol(UnknownScmProtocol.class));
}
private static class DummyHttpProtocol extends HttpScmProtocol {
public DummyHttpProtocol(Repository repository) {
private final boolean anonymousEnabled;
public DummyHttpProtocol(Repository repository, boolean anonymousEnabled) {
super(repository, "");
this.anonymousEnabled = anonymousEnabled;
}
@Override
public void serve(HttpServletRequest request, HttpServletResponse response, Repository repository, ServletConfig config) {
}
@Override
public boolean isAnonymousEnabled() {
return anonymousEnabled;
}
}
private static class DummyScmProtocolProvider implements ScmProtocolProvider {
private final boolean anonymousEnabled;
public DummyScmProtocolProvider() {
this(true);
}
public DummyScmProtocolProvider(boolean anonymousEnabled) {
this.anonymousEnabled = anonymousEnabled;
}
@Override
public String getType() {
return "git";
@@ -92,9 +147,10 @@ public class RepositoryServiceTest {
@Override
public ScmProtocol get(Repository repository) {
return new DummyHttpProtocol(repository);
return new DummyHttpProtocol(repository, anonymousEnabled);
}
}
private interface UnknownScmProtocol extends ScmProtocol {}
private interface UnknownScmProtocol extends ScmProtocol {
}
}

View File

@@ -27,15 +27,18 @@ package sonia.scm.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import org.apache.shiro.authc.AuthenticationInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.AlreadyExistsException;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.user.ExternalUserConverter;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.web.security.AdministrationContext;
@@ -46,7 +49,9 @@ import java.io.IOException;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -68,8 +73,13 @@ public class SyncingRealmHelperTest {
@Mock
private UserManager userManager;
@Mock
private ExternalUserConverter converter;
private SyncingRealmHelper helper;
private SyncingRealmHelper helperWithConverters;
/**
* Mock {@link AdministrationContext} and create object under test.
*/
@@ -94,6 +104,7 @@ public class SyncingRealmHelperTest {
};
helper = new SyncingRealmHelper(ctx, userManager, groupManager);
helperWithConverters = new SyncingRealmHelper(ctx, userManager, groupManager, ImmutableSet.of(converter));
}
/**
@@ -140,10 +151,15 @@ public class SyncingRealmHelperTest {
*/
@Test
public void testStoreUserCreate() {
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
User user = new User("tricia");
helper.store(user);
verify(userManager, times(1)).create(user);
verify(userManager, times(1)).create(userArgumentCaptor.capture());
User value = userArgumentCaptor.getValue();
assertEquals(user.getDisplayName(), value.getDisplayName());
assertEquals(user.getName(), value.getName());
}
/**
@@ -151,9 +167,10 @@ public class SyncingRealmHelperTest {
*/
@Test(expected = IllegalStateException.class)
public void testStoreUserFailure() {
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
User user = new User("tricia");
doThrow(AlreadyExistsException.class).when(userManager).create(user);
doThrow(AlreadyExistsException.class).when(userManager).create(userArgumentCaptor.capture());
helper.store(user);
}
@@ -170,6 +187,23 @@ public class SyncingRealmHelperTest {
verify(userManager, times(1)).modify(user);
}
/**
* Tests {@link SyncingRealmHelper#store(User)} with an existing user.
*/
@Test
public void testConvertUser(){
User zaphod = new User("zaphod");
when(converter.convert(any())).thenReturn(zaphod);
when(userManager.contains("tricia")).thenReturn(Boolean.TRUE);
User user = new User("tricia");
helperWithConverters.store(user);
verify(converter).convert(user);
verify(userManager, times(1)).modify(zaphod);
}
@Test
public void builderShouldSetValues() {

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.web;
//~--- non-JDK imports --------------------------------------------------------
@@ -89,7 +89,7 @@ public class UserAgentParserTest
UserAgent ua = parser.parse(UA_1);
assertEquals(Charsets.ISO_8859_1, ua.getBasicAuthenticationCharset());
assertTrue(ua.isBrowser());
assertFalse(ua.isBrowser());
}
/**
@@ -99,11 +99,11 @@ public class UserAgentParserTest
@Test
public void testParse()
{
UserAgent ua = UserAgent.builder("UA1").build();
UserAgent ua = UserAgent.other("UA1").build();
when(provider1.parseUserAgent(UA_1)).thenReturn(ua);
UserAgent ua2 = UserAgent.builder("UA2").build();
UserAgent ua2 = UserAgent.other("UA2").build();
when(provider2.parseUserAgent(UA_2)).thenReturn(ua2);
@@ -120,7 +120,7 @@ public class UserAgentParserTest
{
when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(UA_2);
UserAgent ua = UserAgent.builder("UA2").build();
UserAgent ua = UserAgent.other("UA2").build();
when(provider1.parseUserAgent(UA_2)).thenReturn(ua);
assertEquals(ua, parser.parse(request));
@@ -144,7 +144,7 @@ public class UserAgentParserTest
@Test
public void testParseWithCache()
{
UserAgent ua = UserAgent.builder("UA").build();
UserAgent ua = UserAgent.other("UA").build();
when(cache.get(UA_1)).thenReturn(ua);
assertEquals(ua, parser.parse(UA_1));

View File

@@ -69,8 +69,8 @@ class HttpProtocolServletAuthenticationFilterBaseTest {
@Mock
private FilterChain filterChain;
private UserAgent nonBrowser = UserAgent.builder("i'm not a browser").browser(false).build();
private UserAgent browser = UserAgent.builder("i am a browser").browser(true).build();
private UserAgent nonBrowser = UserAgent.other("i'm not a browser").build();
private UserAgent browser = UserAgent.browser("i am a browser").build();
@BeforeEach
void setUpObjectUnderTest() {