fix review findings

This commit is contained in:
Eduard Heimbuch
2020-08-11 10:34:29 +02:00
parent a46d8c4749
commit c1cfff603b
63 changed files with 578 additions and 494 deletions

View File

@@ -32,8 +32,8 @@ import java.util.List;
public class AuthenticationRequestDto {
@FormParam("grantType")
@JsonProperty("grantType")
@FormParam("grant_type")
@JsonProperty("grant_type")
private String grantType;
@FormParam("username")
@@ -69,7 +69,7 @@ public class AuthenticationRequestDto {
}
public boolean isValid() {
// password is currently the only valid grantType
// password is currently the only valid grant_type
return "password".equals(grantType) && !Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(password);
}
}

View File

@@ -46,6 +46,7 @@ public class ConfigDto extends HalRepresentation {
private String realmDescription;
private boolean disableGroupingGrid;
private String dateFormat;
private boolean anonymousAccessEnabled;
private AnonymousMode anonymousMode;
private String baseUrl;
private boolean forceBaseUrl;

View File

@@ -21,11 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.AnonymousMode;
// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection.
@SuppressWarnings("squid:S3306")
@@ -33,4 +36,15 @@ import sonia.scm.config.ScmConfiguration;
public abstract class ConfigDtoToScmConfigurationMapper {
public abstract ScmConfiguration map(ConfigDto dto);
@AfterMapping // Should map anonymous mode from old flag if not send explicit
void mapAnonymousMode(@MappingTarget ScmConfiguration config, ConfigDto configDto) {
if (configDto.getAnonymousMode() == null) {
if (configDto.isAnonymousAccessEnabled()) {
config.setAnonymousMode(AnonymousMode.PROTOCOL_ONLY);
} else {
config.setAnonymousMode(AnonymousMode.OFF);
}
}
}
}

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.api.v2.resources;
import io.swagger.v3.oas.annotations.Operation;
@@ -29,7 +29,6 @@ import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.apache.shiro.SecurityUtils;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.group.GroupPermissions;

View File

@@ -123,9 +123,17 @@ public class IndexDtoGenerator extends HalAppenderMapper {
}
private boolean shouldAppendSubjectRelatedLinks() {
return (SecurityUtils.getSubject().isAuthenticated()
&& !Authentications.isAuthenticatedSubjectAnonymous())
|| (Authentications.isAuthenticatedSubjectAnonymous()
&& configuration.getAnonymousMode() == AnonymousMode.FULL);
return isAuthenticatedSubjectNotAnonymous()
|| isAuthenticatedSubjectAllowedToBeAnonymous();
}
private boolean isAuthenticatedSubjectAllowedToBeAnonymous() {
return Authentications.isAuthenticatedSubjectAnonymous()
&& configuration.getAnonymousMode() == AnonymousMode.FULL;
}
private boolean isAuthenticatedSubjectNotAnonymous() {
return SecurityUtils.getSubject().isAuthenticated()
&& !Authentications.isAuthenticatedSubjectAnonymous();
}
}

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.api.v2.resources;
import de.otto.edison.hal.Embedded;
@@ -30,7 +30,6 @@ import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import sonia.scm.group.GroupCollector;
import sonia.scm.security.Authentications;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.user.UserPermissions;
@@ -83,16 +82,14 @@ public class MeDtoFactory extends HalAppenderMapper {
private MeDto createDto(User user) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
if (isNotAnonymous(user)) {
if (UserPermissions.delete(user).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.me().delete(user.getName())));
}
if (UserPermissions.modify(user).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.me().update(user.getName())));
}
if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) {
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
}
if (UserPermissions.delete(user).isPermitted()) {
linksBuilder.single(link("delete", resourceLinks.me().delete(user.getName())));
}
if (UserPermissions.modify(user).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.me().update(user.getName())));
}
if (userManager.isTypeDefault(user) && UserPermissions.changePassword(user).isPermitted()) {
linksBuilder.single(link("password", resourceLinks.me().passwordChange()));
}
Embedded.Builder embeddedBuilder = embeddedBuilder();
@@ -100,8 +97,4 @@ public class MeDtoFactory extends HalAppenderMapper {
return new MeDto(linksBuilder.build(), embeddedBuilder.build());
}
private boolean isNotAnonymous(User user) {
return !Authentications.isSubjectAnonymous(user.getName());
}
}

View File

@@ -27,9 +27,12 @@ package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import org.mapstruct.AfterMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Named;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.AnonymousMode;
import javax.inject.Inject;
@@ -44,6 +47,15 @@ public abstract class ScmConfigurationToConfigDtoMapper extends BaseMapper<ScmCo
@Inject
private ResourceLinks resourceLinks;
@Mapping(target = "anonymousAccessEnabled", source = "anonymousMode", qualifiedByName = "mapAnonymousAccess")
@Mapping(target = "attributes", ignore = true)
public abstract ConfigDto map(ScmConfiguration scmConfiguration);
@Named("mapAnonymousAccess")
boolean mapAnonymousAccess(AnonymousMode anonymousMode) {
return anonymousMode != AnonymousMode.OFF;
}
@AfterMapping
void appendLinks(ScmConfiguration config, @MappingTarget ConfigDto target) {
Links.Builder linksBuilder = linkingTo().self(resourceLinks.config().self());

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.api.v2.resources;
import io.swagger.v3.oas.annotations.Operation;
@@ -30,7 +30,6 @@ import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.apache.shiro.authc.credential.PasswordService;
import sonia.scm.group.GroupPermissions;
import sonia.scm.search.SearchRequest;
import sonia.scm.search.SearchUtil;
import sonia.scm.user.User;

View File

@@ -92,6 +92,7 @@ public class BearerRealm extends AuthenticatingRealm
checkArgument(token instanceof BearerToken, "%s is required", BearerToken.class);
BearerToken bt = (BearerToken) token;
AccessToken accessToken = tokenResolver.resolve(bt);
return helper.authenticationInfoBuilder(accessToken.getSubject())

View File

@@ -21,22 +21,24 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import java.util.Set;
import javax.inject.Inject;
import org.apache.shiro.authc.AuthenticationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.plugin.Extension;
import javax.inject.Inject;
import java.util.Set;
/**
* Jwt implementation of {@link AccessTokenResolver}.
*
*
* @author Sebastian Sdorra
* @since 2.0.0
*/
@@ -47,7 +49,7 @@ public final class JwtAccessTokenResolver implements AccessTokenResolver {
* the logger for JwtAccessTokenResolver
*/
private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenResolver.class);
private final SecureKeyResolver keyResolver;
private final Set<AccessTokenValidator> validators;
@@ -56,7 +58,7 @@ public final class JwtAccessTokenResolver implements AccessTokenResolver {
this.keyResolver = keyResolver;
this.validators = validators;
}
@Override
public JwtAccessToken resolve(BearerToken bearerToken) {
try {
@@ -71,6 +73,8 @@ public final class JwtAccessTokenResolver implements AccessTokenResolver {
validate(token);
return token;
} catch (ExpiredJwtException ex) {
throw new TokenExpiredException("The jwt token has been expired", ex);
} catch (JwtException ex) {
throw new AuthenticationException("signature is invalid", ex);
}
@@ -92,5 +96,5 @@ public final class JwtAccessTokenResolver implements AccessTokenResolver {
private String createValidationFailedMessage(AccessTokenValidator validator, AccessToken accessToken) {
return String.format("token %s is invalid, marked by validator %s", accessToken.getId(), validator.getClass());
}
}

View File

@@ -29,6 +29,7 @@ import lombok.NoArgsConstructor;
import sonia.scm.SCMContextProvider;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.migration.UpdateStep;
import sonia.scm.plugin.Extension;
import sonia.scm.security.AnonymousMode;
import sonia.scm.store.ConfigurationStore;
import sonia.scm.store.ConfigurationStoreFactory;
@@ -41,11 +42,12 @@ import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import static sonia.scm.version.Version.parse;
@Extension
public class AnonymousModeUpdateStep implements UpdateStep {
private final SCMContextProvider contextProvider;
@@ -62,12 +64,14 @@ public class AnonymousModeUpdateStep implements UpdateStep {
Path configFile = determineConfigDirectory().resolve("config" + StoreConstants.FILE_EXTENSION);
if (configFile.toFile().exists()) {
PreUpdateScmConfiguration oldConfig = getPreUpdateScmConfigurationFromOldConfig(configFile);
ScmConfiguration config = configStore.get();
if (getPreUpdateScmConfigurationFromOldConfig(configFile).isAnonymousAccessEnabled()) {
if (oldConfig.isAnonymousAccessEnabled()) {
config.setAnonymousMode(AnonymousMode.PROTOCOL_ONLY);
} else {
config.setAnonymousMode(AnonymousMode.OFF);
}
configStore.set(config);
}
}
@@ -87,7 +91,7 @@ public class AnonymousModeUpdateStep implements UpdateStep {
}
private Path determineConfigDirectory() {
return new File(contextProvider.getBaseDirectory(), StoreConstants.CONFIG_DIRECTORY_NAME).toPath();
return contextProvider.resolve(Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME));
}
@XmlRootElement(name = "scm-config")