Authorization and Authentication refactoring and tests
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
00becacd47
commit
415687c601
47 changed files with 886 additions and 388 deletions
|
@ -1,10 +1,10 @@
|
|||
package net.friedl.fling;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
@ -16,9 +16,10 @@ import net.friedl.fling.model.json.PathSerializer;
|
|||
|
||||
@Configuration
|
||||
public class FlingConfiguration {
|
||||
|
||||
@Bean
|
||||
public MessageDigest keyHashDigest() throws NoSuchAlgorithmException {
|
||||
return MessageDigest.getInstance("SHA-512");
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new Argon2PasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
package net.friedl.fling.security;
|
||||
package net.friedl.fling;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
@ -15,26 +14,18 @@ import lombok.Data;
|
|||
@Configuration
|
||||
@ConfigurationProperties("fling.security")
|
||||
public class FlingSecurityConfiguration {
|
||||
private List<String> allowedOrigins;
|
||||
|
||||
private String adminUser;
|
||||
|
||||
private String adminPassword;
|
||||
|
||||
private String signingKey;
|
||||
|
||||
private Long jwtExpiration;
|
||||
@Bean
|
||||
public JwtParser jwtParser() {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(jwtSigningKey())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Key jwtSigningKey() {
|
||||
byte[] key = signingKey.getBytes(StandardCharsets.UTF_8);
|
||||
return Keys.hmacShaKeyFor(key);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtParser jwtParser(Key jwtSignigKey) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(jwtSignigKey)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import org.springframework.core.io.Resource;
|
|||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
@ -27,6 +28,7 @@ import net.friedl.fling.service.archive.ArchiveService;
|
|||
@RestController
|
||||
@RequestMapping("/api/artifacts")
|
||||
@Tag(name = "artifact", description = "Operations on /api/artifacts")
|
||||
@Validated
|
||||
public class ArtifactController {
|
||||
|
||||
private ArtifactService artifactService;
|
||||
|
@ -52,7 +54,8 @@ public class ArtifactController {
|
|||
|
||||
@RequestBody(content = @Content(schema = @Schema(type = "string", format = "binary")))
|
||||
@PostMapping(path = "/{id}/data")
|
||||
public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request) throws IOException {
|
||||
public void uploadArtifactData(@PathVariable UUID id, HttpServletRequest request)
|
||||
throws IOException {
|
||||
archiveService.storeArtifact(id, request.getInputStream());
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package net.friedl.fling.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import net.friedl.fling.model.dto.AdminAuthDto;
|
||||
import net.friedl.fling.model.dto.UserAuthDto;
|
||||
import net.friedl.fling.service.AuthenticationService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@Tag(name = "auth", description = "Operations on /api/auth")
|
||||
@Validated
|
||||
public class AuthenticationController {
|
||||
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
@Autowired
|
||||
public AuthenticationController(AuthenticationService authenticationService) {
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
@Operation(description = "Authenticates the fling admin by username and password")
|
||||
@ApiResponse(responseCode = "200",
|
||||
description = "JWT Token authenticating the admin of this fling instance")
|
||||
@ApiResponse(responseCode = "403",
|
||||
description = "Authentication failed, username or password are wrong")
|
||||
@PostMapping(path = "/admin")
|
||||
public String authenticateOwner(@RequestBody AdminAuthDto adminAuthDto) {
|
||||
return authenticationService.authenticate(adminAuthDto)
|
||||
.orElseThrow(() -> new AccessDeniedException("Wrong username or password"));
|
||||
}
|
||||
|
||||
@Operation(description = "Authenticates a fling user for a fling via code")
|
||||
@ApiResponse(responseCode = "200",
|
||||
description = "JWT Token authenticating the user for a fling")
|
||||
@ApiResponse(responseCode = "403",
|
||||
description = "Authentication failed, the provided code for the fling is wrong")
|
||||
@ApiResponse(responseCode = "404",
|
||||
description = "No fling for the given share id exists")
|
||||
@PostMapping("/user")
|
||||
public String authenticateUser(@RequestBody UserAuthDto userAuthDto) {
|
||||
return authenticationService.authenticate(userAuthDto)
|
||||
.orElseThrow(() -> new AccessDeniedException("Wrong username or password"));
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package net.friedl.fling.controller;
|
|||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
@ -51,12 +52,13 @@ public class FlingController {
|
|||
}
|
||||
|
||||
@PostMapping
|
||||
public FlingDto postFling(@RequestBody FlingDto flingDto) {
|
||||
public FlingDto postFling(@RequestBody @Valid FlingDto flingDto) {
|
||||
return flingService.create(flingDto);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/artifact")
|
||||
public ArtifactDto postArtifact(@PathVariable UUID id, @RequestBody ArtifactDto artifactDto) {
|
||||
public ArtifactDto postArtifact(@PathVariable UUID id,
|
||||
@RequestBody @Valid ArtifactDto artifactDto) {
|
||||
return artifactService.create(id, artifactDto);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package net.friedl.fling.model.dto;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "AdminAuth")
|
||||
public class AdminAuthDto {
|
||||
@NotNull
|
||||
private String adminName;
|
||||
|
||||
@NotNull
|
||||
private String adminPassword;
|
||||
}
|
|
@ -31,7 +31,6 @@ public class FlingDto {
|
|||
private Instant creationTime = Instant.now();
|
||||
|
||||
@Schema(description = "Share id of the fling. Used in the share link.")
|
||||
@NotNull
|
||||
private String shareId;
|
||||
|
||||
@Schema(description = "Authentication code for password protecting a fling.")
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package net.friedl.fling.model.dto;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "UserAuth")
|
||||
public class UserAuthDto {
|
||||
@NotNull
|
||||
String shareId;
|
||||
|
||||
@NotNull
|
||||
String authCode;
|
||||
}
|
|
@ -19,7 +19,8 @@ public class PathSerializer extends StdSerializer<Path> {
|
|||
private static final long serialVersionUID = -1003917305429893614L;
|
||||
|
||||
@Override
|
||||
public void serialize(Path value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||
public void serialize(Path value, JsonGenerator gen, SerializerProvider provider)
|
||||
throws IOException {
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ import net.friedl.fling.persistence.entities.ArtifactEntity;
|
|||
@Mapper(componentModel = "spring")
|
||||
public interface ArtifactMapper {
|
||||
ArtifactDto map(ArtifactEntity artifactEntity);
|
||||
|
||||
ArtifactEntity map(ArtifactDto artifactDto);
|
||||
|
||||
List<ArtifactDto> mapEntities(List<ArtifactEntity> artifactEntities);
|
||||
|
||||
List<ArtifactEntity> mapDtos(List<ArtifactDto> artifactDtos);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@ import net.friedl.fling.persistence.entities.FlingEntity;
|
|||
@Mapper(componentModel = "spring")
|
||||
public interface FlingMapper {
|
||||
FlingDto map(FlingEntity flingEntity);
|
||||
|
||||
FlingEntity map(FlingDto flingDto);
|
||||
|
||||
List<FlingDto> mapEntities(List<FlingEntity> flingEntities);
|
||||
|
||||
List<FlingEntity> mapDtos(List<FlingDto> flingDtos);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ import lombok.Setter;
|
|||
|
||||
@Entity
|
||||
@Table(name = "Artifact")
|
||||
@Getter @Setter
|
||||
@Getter
|
||||
@Setter
|
||||
public class ArtifactEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
|
|
|
@ -15,7 +15,8 @@ import lombok.Setter;
|
|||
|
||||
@Entity
|
||||
@Table(name = "Fling")
|
||||
@Getter @Setter
|
||||
@Getter
|
||||
@Setter
|
||||
public class FlingEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
package net.friedl.fling.security;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.stereotype.Service;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.friedl.fling.security.authentication.FlingToken;
|
||||
import net.friedl.fling.security.authentication.dto.UserAuthDto;
|
||||
import net.friedl.fling.service.FlingService;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AuthorizationService {
|
||||
private FlingService flingService;
|
||||
|
||||
@Autowired
|
||||
public AuthorizationService(FlingService flingService) {
|
||||
this.flingService = flingService;
|
||||
}
|
||||
|
||||
public boolean allowUpload(UUID flingId, AbstractAuthenticationToken token) {
|
||||
if (!(token instanceof FlingToken)) {
|
||||
log.warn("Authorization attempt without fling token for {}. Authorization denied.", flingId);
|
||||
return false;
|
||||
}
|
||||
|
||||
FlingToken flingToken = (FlingToken) token;
|
||||
if (FlingAuthority.FLING_OWNER.name()
|
||||
.equals(flingToken.getGrantedFlingAuthority().getAuthority())) {
|
||||
log.debug("Owner authorized for upload [id = {}]", flingId);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean uploadAllowed = flingService.getById(flingId).getAllowUpload();
|
||||
boolean authorized = uploadAllowed
|
||||
&& flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId);
|
||||
|
||||
log.debug("User {} authorized for upload [id = {}]", authorized ? "" : "not", flingId);
|
||||
|
||||
return authorized;
|
||||
}
|
||||
|
||||
public boolean allowFlingAccess(UUID flingId, AbstractAuthenticationToken token) {
|
||||
if (!(token instanceof FlingToken)) {
|
||||
log.warn("Authorization attempt without fling token for {}. Authorization denied.", flingId);
|
||||
return false;
|
||||
}
|
||||
|
||||
FlingToken flingToken = (FlingToken) token;
|
||||
if (FlingAuthority.FLING_OWNER.name()
|
||||
.equals(flingToken.getGrantedFlingAuthority().getAuthority())) {
|
||||
log.debug("Owner authorized for fling access [id = {}]", flingId);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean authorized = flingToken.getGrantedFlingAuthority().getFlingId().equals(flingId);
|
||||
log.debug("User {} authorized for fling access [id = {}]", authorized ? "" : "not", flingId);
|
||||
|
||||
return authorized;
|
||||
}
|
||||
|
||||
public boolean allowFlingAccess(UserAuthDto userAuth, String shareId) {
|
||||
boolean authorized = userAuth.getShareId().equals(shareId);
|
||||
log.debug("User {} authorized for fling access [shareId = {}]", authorized ? "" : "not",
|
||||
shareId);
|
||||
|
||||
return authorized;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package net.friedl.fling.security;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
public enum FlingAuthorities {
|
||||
FLING_ADMIN("admin"), FLING_USER("user");
|
||||
|
||||
String authority;
|
||||
|
||||
FlingAuthorities(String authority) {
|
||||
this.authority = authority;
|
||||
}
|
||||
|
||||
public boolean verify(String authority) {
|
||||
return this.authority.equals(authority);
|
||||
}
|
||||
|
||||
public boolean verify(AbstractAuthenticationToken authenticationToken) {
|
||||
return authenticationToken.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.anyMatch(this.authority::equals);
|
||||
}
|
||||
|
||||
public boolean verify(GrantedAuthority grantedAuthority) {
|
||||
return this.authority.equals(grantedAuthority.getAuthority());
|
||||
}
|
||||
|
||||
public String getAuthority() {
|
||||
return authority;
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package net.friedl.fling.security;
|
||||
|
||||
public enum FlingAuthority {
|
||||
FLING_OWNER, FLING_USER
|
||||
}
|
|
@ -3,6 +3,7 @@ package net.friedl.fling.security;
|
|||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
@ -16,7 +17,9 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.friedl.fling.FlingSecurityConfiguration;
|
||||
import net.friedl.fling.security.authentication.JwtAuthenticationFilter;
|
||||
import net.friedl.fling.service.AuthorizationService;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
|
@ -24,9 +27,11 @@ import net.friedl.fling.security.authentication.JwtAuthenticationFilter;
|
|||
@Getter
|
||||
@Setter
|
||||
public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
@Value("fling.security.allowedOrigins")
|
||||
private List<String> allowedOrigins;
|
||||
|
||||
private JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
private AuthorizationService authorizationService;
|
||||
private FlingSecurityConfiguration securityConfiguration;
|
||||
|
||||
@Autowired
|
||||
public FlingWebSecurityConfigurer(JwtAuthenticationFilter jwtAuthenticationFilter,
|
||||
|
@ -35,7 +40,6 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
|||
|
||||
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||
this.authorizationService = authorizationService;
|
||||
this.securityConfiguration = securityConfiguraiton;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,7 +95,7 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
|||
// And lastly, the owner is allowed everything
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/**")
|
||||
.hasAuthority(FlingAuthority.FLING_OWNER.name());
|
||||
.hasAuthority(FlingAuthorities.FLING_ADMIN.getAuthority());
|
||||
|
||||
//@formatter:on
|
||||
}
|
||||
|
@ -100,10 +104,10 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
|||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
// see https://stackoverflow.com/a/43559266
|
||||
|
||||
log.info("Allowed origins: {}", securityConfiguration.getAllowedOrigins());
|
||||
log.info("Allowed origins: {}", allowedOrigins);
|
||||
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(securityConfiguration.getAllowedOrigins());
|
||||
configuration.setAllowedOrigins(allowedOrigins);
|
||||
configuration.setAllowedMethods(List.of("*"));
|
||||
|
||||
// setAllowCredentials(true) is important, otherwise:
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package net.friedl.fling.security.authentication;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import net.friedl.fling.security.authentication.dto.OwnerAuthDto;
|
||||
import net.friedl.fling.security.authentication.dto.UserAuthDto;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@Tag(name = "auth", description = "Operations on /api/auth")
|
||||
public class AuthenticationController {
|
||||
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
@Autowired
|
||||
public AuthenticationController(AuthenticationService authenticationService) {
|
||||
this.authenticationService = authenticationService;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/owner")
|
||||
public String authenticateOwner(@RequestBody OwnerAuthDto ownerAuthDto) {
|
||||
return authenticationService.authenticate(ownerAuthDto);
|
||||
}
|
||||
|
||||
@PostMapping("/user")
|
||||
public String authenticateUser(@RequestBody UserAuthDto userAuthDto) {
|
||||
return authenticationService.authenticate(userAuthDto);
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package net.friedl.fling.security.authentication;
|
||||
|
||||
import java.security.Key;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Service;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import net.friedl.fling.model.dto.FlingDto;
|
||||
import net.friedl.fling.security.FlingAuthority;
|
||||
import net.friedl.fling.security.FlingSecurityConfiguration;
|
||||
import net.friedl.fling.security.authentication.dto.OwnerAuthDto;
|
||||
import net.friedl.fling.security.authentication.dto.UserAuthDto;
|
||||
import net.friedl.fling.service.FlingService;
|
||||
|
||||
@Service
|
||||
public class AuthenticationService {
|
||||
private FlingService flingService;
|
||||
private JwtParser jwtParser;
|
||||
private Key signingKey;
|
||||
private FlingSecurityConfiguration securityConfig;
|
||||
|
||||
@Autowired
|
||||
public AuthenticationService(JwtParser jwtParser, Key signingKey, FlingService flingService,
|
||||
FlingSecurityConfiguration securityConfig) {
|
||||
this.flingService = flingService;
|
||||
this.jwtParser = jwtParser;
|
||||
this.signingKey = signingKey;
|
||||
this.securityConfig = securityConfig;
|
||||
}
|
||||
|
||||
public String authenticate(OwnerAuthDto ownerAuth) {
|
||||
if (!securityConfig.getAdminUser().equals(ownerAuth.getUsername())) {
|
||||
throw new AccessDeniedException("Wrong credentials");
|
||||
}
|
||||
|
||||
if (!securityConfig.getAdminPassword().equals(ownerAuth.getPassword())) {
|
||||
throw new AccessDeniedException("Wrong credentials");
|
||||
}
|
||||
|
||||
return makeBaseBuilder()
|
||||
.setSubject("owner")
|
||||
.compact();
|
||||
}
|
||||
|
||||
public String authenticate(UserAuthDto userAuth) {
|
||||
FlingDto flingDto = flingService.getByShareId(userAuth.getShareId());
|
||||
String authCode = userAuth.getCode();
|
||||
|
||||
if (!flingService.validateAuthCode(flingDto.getId(), authCode)) {
|
||||
throw new AccessDeniedException("Wrong fling code");
|
||||
}
|
||||
|
||||
return makeBaseBuilder()
|
||||
.setSubject("user")
|
||||
.claim("sid", flingDto.getShareId())
|
||||
.compact();
|
||||
|
||||
}
|
||||
|
||||
public Authentication parseAuthentication(String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
|
||||
FlingAuthority authority;
|
||||
UUID flingId;
|
||||
|
||||
switch (claims.getSubject()) {
|
||||
case "owner":
|
||||
authority = FlingAuthority.FLING_OWNER;
|
||||
flingId = null;
|
||||
break;
|
||||
case "user":
|
||||
authority = FlingAuthority.FLING_USER;
|
||||
String sid = claims.get("sid", String.class);
|
||||
flingId = flingService.getByShareId(sid).getId();
|
||||
break;
|
||||
default:
|
||||
throw new BadCredentialsException("Invalid token");
|
||||
}
|
||||
|
||||
return new FlingToken(new GrantedFlingAuthority(authority, flingId));
|
||||
}
|
||||
|
||||
private JwtBuilder makeBaseBuilder() {
|
||||
return Jwts.builder()
|
||||
.setIssuedAt(Date.from(Instant.now()))
|
||||
.setExpiration(Date.from(Instant.now().plusSeconds(securityConfig.getJwtExpiration())))
|
||||
.signWith(signingKey);
|
||||
}
|
||||
|
||||
private Claims parseClaims(String token) {
|
||||
return jwtParser.parseClaimsJws(token).getBody();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package net.friedl.fling.security.authentication;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import net.friedl.fling.security.FlingAuthorities;
|
||||
|
||||
public class FlingAdminAuthority implements GrantedAuthority {
|
||||
|
||||
private static final long serialVersionUID = -4605768612393081070L;
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return FlingAuthorities.FLING_ADMIN.getAuthority();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,25 +1,36 @@
|
|||
package net.friedl.fling.security.authentication;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
public class FlingToken extends AbstractAuthenticationToken {
|
||||
|
||||
private static final long serialVersionUID = -1112423505610346583L;
|
||||
private GrantedFlingAuthority grantedFlingAuthority;
|
||||
private String jwtToken;
|
||||
|
||||
public FlingToken(GrantedFlingAuthority authority) {
|
||||
public FlingToken(GrantedAuthority authority, String jwtToken) {
|
||||
super(List.of(authority));
|
||||
this.grantedFlingAuthority = authority;
|
||||
this.jwtToken = jwtToken;
|
||||
}
|
||||
|
||||
public GrantedFlingAuthority getGrantedFlingAuthority() {
|
||||
return this.grantedFlingAuthority;
|
||||
public boolean authorizedForFling(UUID id) {
|
||||
for (GrantedAuthority grantedAuthority : getAuthorities()) {
|
||||
if (grantedAuthority instanceof FlingAdminAuthority) return true;
|
||||
|
||||
if (!(grantedAuthority instanceof FlingUserAuthority)) continue;
|
||||
|
||||
UUID grantedFlingId = ((FlingUserAuthority) grantedAuthority).getFlingId();
|
||||
if (grantedFlingId.equals(id)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return null;
|
||||
public String getCredentials() {
|
||||
return this.jwtToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package net.friedl.fling.security.authentication;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import net.friedl.fling.security.FlingAuthorities;
|
||||
|
||||
public class FlingUserAuthority implements GrantedAuthority {
|
||||
private static final long serialVersionUID = -1814514234042184275L;
|
||||
|
||||
private UUID flingId;
|
||||
|
||||
public FlingUserAuthority(UUID flingId) {
|
||||
this.flingId = flingId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return FlingAuthorities.FLING_USER.getAuthority();
|
||||
}
|
||||
|
||||
public UUID getFlingId() {
|
||||
return flingId;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package net.friedl.fling.security.authentication;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import net.friedl.fling.security.FlingAuthority;
|
||||
|
||||
/**
|
||||
* Authority granting access to a fling
|
||||
*
|
||||
* @author Armin Friedl <dev@friedl.net>
|
||||
*/
|
||||
public class GrantedFlingAuthority implements GrantedAuthority {
|
||||
|
||||
private static final long serialVersionUID = -1552301479158714777L;
|
||||
|
||||
private FlingAuthority authority;
|
||||
private UUID flingId;
|
||||
|
||||
public GrantedFlingAuthority(FlingAuthority authority, UUID flingId) {
|
||||
this.authority = authority;
|
||||
this.flingId = flingId;
|
||||
}
|
||||
|
||||
public UUID getFlingId() {
|
||||
return this.flingId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return authority.name();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
package net.friedl.fling.security.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.friedl.fling.service.AuthenticationService;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
|
@ -34,7 +36,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
String header = request.getHeader(HEADER_STRING);
|
||||
|
||||
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
|
||||
log.warn("Could not find bearer token. No JWT authentication.");
|
||||
log.info("Anonymous request for {} {}?{}", request.getMethod(), request.getRequestURL(),
|
||||
request.getQueryString());
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
@ -44,8 +47,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
|
||||
if (securityContext.getAuthentication() == null) {
|
||||
Authentication authentication = authenticationService.parseAuthentication(authToken);
|
||||
securityContext.setAuthentication(authentication);
|
||||
log.info("Authenticating request for {} {}?{}", request.getMethod(), request.getRequestURL(),
|
||||
request.getQueryString());
|
||||
FlingToken token = authenticationService.parseAuthentication(authToken);
|
||||
log.info("Authenticated as {}", token.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")));
|
||||
securityContext.setAuthentication(token);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package net.friedl.fling.security.authentication.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(name = "OwnerAuth")
|
||||
public class OwnerAuthDto {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package net.friedl.fling.security.authentication.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(name = "UserAuth")
|
||||
public class UserAuthDto {
|
||||
String shareId;
|
||||
String code;
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package net.friedl.fling.service;
|
||||
|
||||
import java.security.Key;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.friedl.fling.model.dto.AdminAuthDto;
|
||||
import net.friedl.fling.model.dto.UserAuthDto;
|
||||
import net.friedl.fling.persistence.entities.FlingEntity;
|
||||
import net.friedl.fling.persistence.repositories.FlingRepository;
|
||||
import net.friedl.fling.security.authentication.FlingAdminAuthority;
|
||||
import net.friedl.fling.security.authentication.FlingToken;
|
||||
import net.friedl.fling.security.authentication.FlingUserAuthority;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AuthenticationService {
|
||||
private JwtParser jwtParser;
|
||||
private Key jwtSigningKey;
|
||||
private FlingRepository flingRepository;
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Value("${fling.security.admin-name}")
|
||||
private String adminName;
|
||||
@Value("${fling.security.admin-password}")
|
||||
private String adminPassword;
|
||||
@Value("${fling.security.jwt-expiration}")
|
||||
private Long jwtExpiration;
|
||||
|
||||
@Autowired
|
||||
public AuthenticationService(JwtParser jwtParser, Key jwtSigningKey,
|
||||
PasswordEncoder passwordEncoder, FlingRepository flingRepository) {
|
||||
|
||||
this.jwtParser = jwtParser;
|
||||
this.jwtSigningKey = jwtSigningKey;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.flingRepository = flingRepository;
|
||||
}
|
||||
|
||||
public Optional<String> authenticate(AdminAuthDto adminAuth) {
|
||||
log.info("Authenticating {}", adminAuth.getAdminName());
|
||||
if (!adminName.equals(adminAuth.getAdminName())) {
|
||||
log.debug("Authentication failed for {}", adminAuth.getAdminName());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (!adminPassword.equals(adminAuth.getAdminPassword())) {
|
||||
log.debug("Authentication failed for {}", adminAuth.getAdminName());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
log.debug("Authentication successful for {}", adminAuth.getAdminName());
|
||||
return Optional.of(
|
||||
getJwtBuilder()
|
||||
.setSubject("admin")
|
||||
.compact());
|
||||
}
|
||||
|
||||
public Optional<String> authenticate(UserAuthDto userAuth) {
|
||||
log.info("Authenticating for fling [.shareId={}]", userAuth.getShareId());
|
||||
FlingEntity flingEntity = flingRepository.findByShareId(userAuth.getShareId());
|
||||
String providedAuthCodeHash = passwordEncoder.encode(userAuth.getAuthCode());
|
||||
String actualAuthCodeHash = flingEntity.getAuthCode();
|
||||
|
||||
if (!actualAuthCodeHash.equals(providedAuthCodeHash)) {
|
||||
log.debug("Authentication failed for fling [.shareId={}]", userAuth.getShareId());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
log.debug("Authentication successful for fling [.shareId={}]", userAuth.getShareId());
|
||||
return Optional.of(
|
||||
getJwtBuilder()
|
||||
.setSubject("user")
|
||||
.claim("id", flingEntity.getId())
|
||||
.compact());
|
||||
|
||||
}
|
||||
|
||||
public FlingToken parseAuthentication(String token) {
|
||||
Claims claims = jwtParser.parseClaimsJws(token).getBody();
|
||||
|
||||
switch (claims.getSubject()) {
|
||||
case "owner":
|
||||
return new FlingToken(new FlingAdminAuthority(), token);
|
||||
case "user":
|
||||
UUID grantedFlingId = UUID.fromString(claims.get("id", String.class));
|
||||
return new FlingToken(new FlingUserAuthority(grantedFlingId), token);
|
||||
default:
|
||||
throw new BadCredentialsException("Invalid token");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JwtBuilder. A new builder must be constructed for each JWT creation, because the
|
||||
* builder keeps its state.
|
||||
*
|
||||
* @return A new JwtBuilder with basic default configuration.
|
||||
*/
|
||||
private JwtBuilder getJwtBuilder() {
|
||||
return Jwts.builder()
|
||||
.setIssuedAt(Date.from(Instant.now()))
|
||||
.setExpiration(Date.from(Instant.now().plusSeconds(jwtExpiration)))
|
||||
.signWith(jwtSigningKey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package net.friedl.fling.service;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.stereotype.Service;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.friedl.fling.persistence.repositories.FlingRepository;
|
||||
import net.friedl.fling.security.FlingAuthorities;
|
||||
import net.friedl.fling.security.authentication.FlingToken;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AuthorizationService {
|
||||
private FlingRepository flingRepository;
|
||||
|
||||
@Autowired
|
||||
public AuthorizationService(FlingRepository flingRepository) {
|
||||
this.flingRepository = flingRepository;
|
||||
}
|
||||
|
||||
public boolean allowUpload(UUID flingId, AbstractAuthenticationToken token) {
|
||||
if (!(token instanceof FlingToken)) {
|
||||
log.debug("Token of type {} not allowed. Authentication denied.", token.getClass());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FlingAuthorities.FLING_ADMIN.verify(token)) {
|
||||
log.debug("Owner authorized for upload fling[.id={}]", flingId);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!flingRepository.getOne(flingId).getAllowUpload()) {
|
||||
log.debug("Fling[.id={}] does not not allow uploads");
|
||||
return false;
|
||||
}
|
||||
|
||||
FlingToken flingToken = (FlingToken) token;
|
||||
if (flingToken.authorizedForFling(flingId)) {
|
||||
log.debug("User authorized for upload fling[.id={}]", flingId);
|
||||
return true;
|
||||
}
|
||||
|
||||
log.info("User not authorized for upload fling[.id={}]", flingId);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean allowFlingAccess(UUID flingId, AbstractAuthenticationToken token) {
|
||||
if (!(token instanceof FlingToken)) {
|
||||
log.debug("Token of type {} not allowed. Authentication denied.", token.getClass());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FlingAuthorities.FLING_ADMIN.verify(token)) {
|
||||
log.debug("Owner authorized for fling access [id = {}]", flingId);
|
||||
return true;
|
||||
}
|
||||
|
||||
FlingToken flingToken = (FlingToken) token;
|
||||
if (flingToken.authorizedForFling(flingId)) {
|
||||
log.debug("User authorized for fling access [id = {}]");
|
||||
return true;
|
||||
}
|
||||
|
||||
log.info("User not authorized to access fling[.id={}]", flingId);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
package net.friedl.fling.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.transaction.Transactional;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -26,17 +25,16 @@ public class FlingService {
|
|||
private FlingRepository flingRepository;
|
||||
private FlingMapper flingMapper;
|
||||
private ArchiveService archiveService;
|
||||
private MessageDigest keyHashDigest;
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Autowired
|
||||
public FlingService(FlingRepository flingRepository, FlingMapper flingMapper,
|
||||
ArchiveService archiveService,
|
||||
MessageDigest keyHashDigest) {
|
||||
ArchiveService archiveService, PasswordEncoder passwordEcoder) {
|
||||
|
||||
this.flingRepository = flingRepository;
|
||||
this.flingMapper = flingMapper;
|
||||
this.archiveService = archiveService;
|
||||
this.keyHashDigest = keyHashDigest;
|
||||
this.passwordEncoder = passwordEcoder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,7 +94,7 @@ public class FlingService {
|
|||
|
||||
public boolean validateAuthCode(UUID id, String authCode) {
|
||||
FlingEntity flingEntity = flingRepository.getOne(id);
|
||||
if(StringUtils.hasText(flingEntity.getAuthCode()) != StringUtils.hasText(authCode)) {
|
||||
if (StringUtils.hasText(flingEntity.getAuthCode()) != StringUtils.hasText(authCode)) {
|
||||
return false; // only one of them is empty; implicit null safety check
|
||||
}
|
||||
|
||||
|
@ -106,7 +104,7 @@ public class FlingService {
|
|||
}
|
||||
|
||||
private String hashAuthCode(String authCode) {
|
||||
String hash = new String(Hex.encode(keyHashDigest.digest(authCode.getBytes())));
|
||||
String hash = passwordEncoder.encode(authCode);
|
||||
log.debug("Hashed authentication code to {}", hash);
|
||||
return hash;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
"sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration"
|
||||
},
|
||||
{
|
||||
"name": "fling.security.admin-user",
|
||||
"name": "fling.security.admin-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Username of the admin user/instance owner",
|
||||
"sourceType": "net.friedl.fling.security.FlingWebSecurityConfiguration"
|
||||
|
|
|
@ -28,10 +28,10 @@ fling:
|
|||
- "http://localhost:3000"
|
||||
- "http://localhost:5000"
|
||||
- "http://10.0.2.2:5000"
|
||||
admin-user: "${FLING_ADMIN_USER:admin}"
|
||||
admin-password: "${FLING_ADMIN_PASSWORD:123}"
|
||||
signing-key: "${FLING_SIGNING_KEY:changeitchangeitchangeitchangeit}"
|
||||
jwt-expiration: "${FLING_JWT_EXPIRATION:180000}"
|
||||
admin-name: "adminName"
|
||||
admin-password: "adminPassword"
|
||||
signing-key: "changeitchangeitchangeitchangeit"
|
||||
jwt-expiration: "180000"
|
||||
api:
|
||||
version: "0"
|
||||
server-url: "http://localhost:8080"
|
||||
|
|
|
@ -19,9 +19,15 @@ fling:
|
|||
archive.filesystem.archive-path: "/var/fling/files"
|
||||
security:
|
||||
allowed-origins:
|
||||
- "https://fling.friedl.net"
|
||||
- "https://friedl.net"
|
||||
- "http://localhost:3000"
|
||||
admin-user: "${FLING_ADMIN_USER:admin}"
|
||||
admin-password: "${FLING_ADMIN_PASSWORD:123}"
|
||||
signing-key: "${FLING_SIGNING_KEY:changeitchangeitchangeitchangeit}"
|
||||
jwt-expiration: "${FLING_JWT_EXPIRATION:180000}"
|
||||
- "http://localhost:5000"
|
||||
- "http://10.0.2.2:5000"
|
||||
admin-name: "adminName"
|
||||
admin-password: "adminPassword"
|
||||
signing-key: "changeitchangeitchangeitchangeit"
|
||||
jwt-expiration: "180000"
|
||||
api:
|
||||
version: "0"
|
||||
server-url: "http://localhost:8080"
|
||||
server-description: "API server for dev"
|
|
@ -110,7 +110,8 @@ class ArtifactControllerTest {
|
|||
|
||||
mvc.perform(get("/api/artifacts/{id}/data", ARTIFACT_ID))
|
||||
.andExpect(header().doesNotExist(HttpHeaders.CONTENT_DISPOSITION))
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE))))
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE,
|
||||
not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE))))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
|
@ -120,7 +121,8 @@ class ArtifactControllerTest {
|
|||
|
||||
mvc.perform(get("/api/artifacts/{id}/data", ARTIFACT_ID))
|
||||
.andExpect(header().doesNotExist(HttpHeaders.CONTENT_DISPOSITION))
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE))))
|
||||
.andExpect(header().string(HttpHeaders.CONTENT_TYPE,
|
||||
not(equalTo(MediaType.APPLICATION_OCTET_STREAM_VALUE))))
|
||||
.andExpect(status().isInternalServerError());
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package net.friedl.fling.controller;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.friedl.fling.model.dto.AdminAuthDto;
|
||||
import net.friedl.fling.model.dto.UserAuthDto;
|
||||
import net.friedl.fling.service.AuthenticationService;
|
||||
import net.friedl.fling.service.AuthorizationService;
|
||||
|
||||
@WebMvcTest(controllers = AuthenticationController.class,
|
||||
includeFilters = {@Filter(Configuration.class)})
|
||||
@ActiveProfiles("local")
|
||||
public class AuthenticationControllerTest {
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@MockBean
|
||||
private AuthenticationService authenticationService;
|
||||
|
||||
@MockBean
|
||||
private AuthorizationService authorizationService;
|
||||
|
||||
@Test
|
||||
public void authenticateOwner_noToken_403() throws Exception {
|
||||
AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "123");
|
||||
when(authenticationService.authenticate(any(AdminAuthDto.class))).thenReturn(Optional.empty());
|
||||
|
||||
mvc.perform(post("/api/auth/admin")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(adminAuthDto)))
|
||||
.andExpect(status().is(403));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateOwner_token_ok() throws Exception {
|
||||
AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "123");
|
||||
when(authenticationService.authenticate(any(AdminAuthDto.class)))
|
||||
.thenReturn(Optional.of("token"));
|
||||
|
||||
mvc.perform(post("/api/auth/admin")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(adminAuthDto)))
|
||||
.andExpect(status().is(200))
|
||||
.andExpect(content().string("token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateUser_noToken_403() throws Exception {
|
||||
UserAuthDto userAuthDto = new UserAuthDto("shareId", "authCode");
|
||||
when(authenticationService.authenticate(any(UserAuthDto.class))).thenReturn(Optional.empty());
|
||||
|
||||
mvc.perform(post("/api/auth/user")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(userAuthDto)))
|
||||
.andExpect(status().is(403));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateUser_token_ok() throws Exception {
|
||||
UserAuthDto userAuthDto = new UserAuthDto("shareId", "authCode");
|
||||
when(authenticationService.authenticate(any(UserAuthDto.class)))
|
||||
.thenReturn(Optional.of("token"));
|
||||
|
||||
mvc.perform(post("/api/auth/user")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(userAuthDto)))
|
||||
.andExpect(status().is(200))
|
||||
.andExpect(content().string("token"));
|
||||
}
|
||||
}
|
|
@ -96,6 +96,16 @@ public class FlingControllerTest {
|
|||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postFling_validatesBody_notOk() throws Exception {
|
||||
FlingDto invalidFlingDto = new FlingDto();
|
||||
|
||||
mockMvc.perform(post("/api/fling")
|
||||
.content(mapper.writeValueAsString(invalidFlingDto))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postArtifact_ok() throws Exception {
|
||||
mockMvc.perform(post("/api/fling/{id}/artifact", flingId)
|
||||
|
@ -104,6 +114,16 @@ public class FlingControllerTest {
|
|||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postArtifact_validatesBody_notOk() throws Exception {
|
||||
ArtifactDto invalidArtifactDto = new ArtifactDto();
|
||||
|
||||
mockMvc.perform(post("/api/fling/{id}/artifact", flingId)
|
||||
.content(mapper.writeValueAsString(invalidArtifactDto))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFling_noFlingWithId_notFound() throws Exception {
|
||||
doThrow(EntityNotFoundException.class).when(flingService).getById(flingId);
|
||||
|
|
|
@ -75,8 +75,9 @@ public class FlingDtoTest {
|
|||
assertThat(violation.getMessage()).isEqualTo("must not be null");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSetShareId_null_validationFails() {
|
||||
void testSetShareId_null_validationOk() { // must be nullable to support defaulting in service
|
||||
FlingDto flingDto = FlingDto.builder()
|
||||
.id(new UUID(0L, 0L))
|
||||
.name("test")
|
||||
|
@ -87,10 +88,7 @@ public class FlingDtoTest {
|
|||
|
||||
Set<ConstraintViolation<FlingDto>> constraintViolations = validator.validate(flingDto);
|
||||
|
||||
assertThat(constraintViolations).hasSize(1);
|
||||
ConstraintViolation<FlingDto> violation = constraintViolations.iterator().next();
|
||||
assertThat(violation.getPropertyPath().toString()).isEqualTo("shareId");
|
||||
assertThat(violation.getMessage()).isEqualTo("must not be null");
|
||||
assertThat(constraintViolations).hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -62,7 +62,8 @@ public class ArtifactServiceTest {
|
|||
FlingRepository flingRepository, ArtifactMapper artifactMapper,
|
||||
ArchiveService archiveService) {
|
||||
|
||||
return new ArtifactService(artifactRepository, flingRepository, artifactMapper, archiveService);
|
||||
return new ArtifactService(artifactRepository, flingRepository, artifactMapper,
|
||||
archiveService);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +77,7 @@ public class ArtifactServiceTest {
|
|||
this.artifactEntity2 = new ArtifactEntity();
|
||||
artifactEntity2.setId(UUID.randomUUID());
|
||||
artifactEntity2.setUploadTime(Instant.EPOCH.plus(12000, ChronoUnit.DAYS));
|
||||
artifactEntity2.setPath(Path.of("/","/sub","artifact2"));
|
||||
artifactEntity2.setPath(Path.of("/", "/sub", "artifact2"));
|
||||
|
||||
this.flingEntity = new FlingEntity();
|
||||
flingEntity.setId(UUID.randomUUID());
|
||||
|
@ -87,7 +88,7 @@ public class ArtifactServiceTest {
|
|||
@Override
|
||||
public FlingEntity answer(InvocationOnMock invocation) throws Throwable {
|
||||
FlingEntity flingEntity = invocation.getArgument(0);
|
||||
if(flingEntity.getId() == null) flingEntity.setId(UUID.randomUUID());
|
||||
if (flingEntity.getId() == null) flingEntity.setId(UUID.randomUUID());
|
||||
return flingEntity;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package net.friedl.fling.service;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.security.Key;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.impl.DefaultClaims;
|
||||
import io.jsonwebtoken.impl.DefaultJws;
|
||||
import io.jsonwebtoken.impl.DefaultJwsHeader;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import net.friedl.fling.model.dto.AdminAuthDto;
|
||||
import net.friedl.fling.model.dto.UserAuthDto;
|
||||
import net.friedl.fling.persistence.entities.FlingEntity;
|
||||
import net.friedl.fling.persistence.repositories.FlingRepository;
|
||||
import net.friedl.fling.security.authentication.FlingAdminAuthority;
|
||||
import net.friedl.fling.security.authentication.FlingToken;
|
||||
import net.friedl.fling.security.authentication.FlingUserAuthority;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@TestPropertySource("classpath:/application-test.properties")
|
||||
@ActiveProfiles("test")
|
||||
public class AuthenticationServiceTest {
|
||||
@Autowired
|
||||
public AuthenticationService authenticationService;
|
||||
|
||||
@MockBean
|
||||
private FlingRepository flingRepository;
|
||||
|
||||
@MockBean
|
||||
private JwtParser jwtParser;
|
||||
|
||||
@MockBean
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@TestConfiguration
|
||||
static class FlingServiceTestConfiguration {
|
||||
private Key jwtSigningKey = Keys.hmacShaKeyFor(new byte[32]);
|
||||
|
||||
@Bean
|
||||
public AuthenticationService authenticationService(JwtParser jwtParser,
|
||||
PasswordEncoder passwordEncoder, FlingRepository flingRepository) {
|
||||
return new AuthenticationService(jwtParser, jwtSigningKey, passwordEncoder, flingRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_adminNameDiffers_empty() {
|
||||
AdminAuthDto adminAuthDto = new AdminAuthDto("wrongadmin", "123");
|
||||
|
||||
assertThat(authenticationService.authenticate(adminAuthDto), equalTo(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_passwordDiffers_empty() {
|
||||
AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "wrongpassword");
|
||||
|
||||
assertThat(authenticationService.authenticate(adminAuthDto), equalTo(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_ok() {
|
||||
AdminAuthDto adminAuthDto = new AdminAuthDto("admin", "123");
|
||||
|
||||
assertThat(authenticationService.authenticate(adminAuthDto), not(equalTo(Optional.empty())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_authCodeDiffers_empty() {
|
||||
FlingEntity flingEntity = new FlingEntity();
|
||||
flingEntity.setAuthCode("test");
|
||||
flingEntity.setId(UUID.randomUUID());
|
||||
|
||||
UserAuthDto userAuthDto = new UserAuthDto("shareId", "wrongCode");
|
||||
|
||||
when(flingRepository.findByShareId(any(String.class))).thenReturn(flingEntity);
|
||||
when(passwordEncoder.encode(any(String.class))).thenReturn("wrongCode");
|
||||
|
||||
assertThat(authenticationService.authenticate(userAuthDto), equalTo(Optional.empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticate_authCodeEquals_ok() {
|
||||
FlingEntity flingEntity = new FlingEntity();
|
||||
flingEntity.setAuthCode("authCodeHash");
|
||||
flingEntity.setId(UUID.randomUUID());
|
||||
|
||||
UserAuthDto userAuthDto = UserAuthDto.builder()
|
||||
.authCode("authCode")
|
||||
.shareId("shareId").build();
|
||||
|
||||
when(flingRepository.findByShareId(any(String.class))).thenReturn(flingEntity);
|
||||
when(passwordEncoder.encode(any(String.class))).thenReturn("authCodeHash");
|
||||
|
||||
assertThat(authenticationService.authenticate(userAuthDto), not(equalTo(Optional.empty())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAuthentication_owner_AdminAuthority() {
|
||||
Jws<Claims> jwsClaims = new DefaultJws<>(new DefaultJwsHeader(),
|
||||
new DefaultClaims(Map.of("sub", "owner")), "signature");
|
||||
when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims);
|
||||
|
||||
FlingToken flingToken = authenticationService.parseAuthentication("any");
|
||||
assertThat(flingToken.isAuthenticated(), equalTo(true));
|
||||
// authorized for any fling
|
||||
assertThat(flingToken.authorizedForFling(UUID.randomUUID()), equalTo(true));
|
||||
assertThat(flingToken.getCredentials(), equalTo("any"));
|
||||
assertThat(flingToken.getAuthorities(),
|
||||
hasItem(org.hamcrest.Matchers.any(FlingAdminAuthority.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAuthentication_user_UserAuthorityForId() {
|
||||
Jws<Claims> jwsClaims = new DefaultJws<>(new DefaultJwsHeader(),
|
||||
new DefaultClaims(Map.of("sub", "user", "id", new UUID(0, 0).toString())), "signature");
|
||||
when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims);
|
||||
|
||||
FlingToken flingToken = authenticationService.parseAuthentication("any");
|
||||
assertThat(flingToken.isAuthenticated(), equalTo(true));
|
||||
// authorized for fling in token
|
||||
assertThat(flingToken.authorizedForFling(new UUID(0, 0)), equalTo(true));
|
||||
// not authorized for fling other flings
|
||||
assertThat(flingToken.authorizedForFling(new UUID(0, 1)), equalTo(false));
|
||||
assertThat(flingToken.getCredentials(), equalTo("any"));
|
||||
assertThat(flingToken.getAuthorities(),
|
||||
hasItem(org.hamcrest.Matchers.any(FlingUserAuthority.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAuthentication_unknownSubject_throws() {
|
||||
Jws<Claims> jwsClaims = new DefaultJws<>(new DefaultJwsHeader(),
|
||||
new DefaultClaims(Map.of("sub", "unknownSubject")), "signature");
|
||||
when(jwtParser.parseClaimsJws(any(String.class))).thenReturn(jwsClaims);
|
||||
|
||||
assertThrows(BadCredentialsException.class,
|
||||
() -> authenticationService.parseAuthentication("any"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package net.friedl.fling.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import net.friedl.fling.persistence.entities.FlingEntity;
|
||||
import net.friedl.fling.persistence.repositories.FlingRepository;
|
||||
import net.friedl.fling.security.authentication.FlingAdminAuthority;
|
||||
import net.friedl.fling.security.authentication.FlingToken;
|
||||
import net.friedl.fling.security.authentication.FlingUserAuthority;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class AuthorizationServiceTest {
|
||||
|
||||
@Autowired
|
||||
private AuthorizationService authorizationService;
|
||||
|
||||
@MockBean
|
||||
private FlingRepository flingRepository;
|
||||
|
||||
@TestConfiguration
|
||||
static class FlingServiceTestConfiguration {
|
||||
@Bean
|
||||
public AuthorizationService authorizationService(FlingRepository flingRepository) {
|
||||
return new AuthorizationService(flingRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowUpload_unknownToken_false() {
|
||||
var unkownToken = new AnonymousAuthenticationToken("key", "principal",
|
||||
List.of(new SimpleGrantedAuthority("role")));
|
||||
|
||||
assertFalse(authorizationService.allowUpload(UUID.randomUUID(), unkownToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowUpload_flingAdmin_true() {
|
||||
FlingToken flingToken = new FlingToken(new FlingAdminAuthority(), "jwtToken");
|
||||
assertTrue(authorizationService.allowUpload(UUID.randomUUID(), flingToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowUpload_noAdmin_uploadDisallowed_false() {
|
||||
FlingEntity flingEntity = new FlingEntity();
|
||||
flingEntity.setAllowUpload(false);
|
||||
|
||||
FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken");
|
||||
|
||||
when(flingRepository.getOne(new UUID(0, 0))).thenReturn(flingEntity);
|
||||
|
||||
assertFalse(authorizationService.allowUpload(new UUID(0, 0), flingToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowUpload_noAdmin_uploadAllowed_notAuthorized_false() {
|
||||
FlingEntity flingEntity = new FlingEntity();
|
||||
flingEntity.setAllowUpload(true);
|
||||
|
||||
FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken");
|
||||
|
||||
when(flingRepository.getOne(new UUID(1, 1))).thenReturn(flingEntity);
|
||||
|
||||
// Token: UUID(0,0), Request: UUID(1,1)
|
||||
assertFalse(authorizationService.allowUpload(new UUID(1, 1), flingToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowUpload_noAdmin_uploadAllowed_authorized_true() {
|
||||
FlingEntity flingEntity = new FlingEntity();
|
||||
flingEntity.setAllowUpload(true);
|
||||
|
||||
FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken");
|
||||
|
||||
when(flingRepository.getOne(new UUID(0, 0))).thenReturn(flingEntity);
|
||||
|
||||
// Token: UUID(0,0), Request: UUID(0,0)
|
||||
assertTrue(authorizationService.allowUpload(new UUID(0, 0), flingToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowFlingAccess_unknownToken_false() {
|
||||
var unkownToken = new AnonymousAuthenticationToken("key", "principal",
|
||||
List.of(new SimpleGrantedAuthority("role")));
|
||||
assertFalse(authorizationService.allowFlingAccess(UUID.randomUUID(), unkownToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowFlingAcess_flingAdmin_true() {
|
||||
FlingToken flingToken = new FlingToken(new FlingAdminAuthority(), "jwtToken");
|
||||
assertTrue(authorizationService.allowFlingAccess(UUID.randomUUID(), flingToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowFlingAcess_flingUser_notAuthorizedForId_false() {
|
||||
FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken");
|
||||
assertFalse(authorizationService.allowFlingAccess(new UUID(1, 1), flingToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowFlingAcess_flingUser_authorizedForId_true() {
|
||||
FlingToken flingToken = new FlingToken(new FlingUserAuthority(new UUID(0, 0)), "jwtToken");
|
||||
assertTrue(authorizationService.allowFlingAccess(new UUID(0, 0), flingToken));
|
||||
}
|
||||
}
|
|
@ -11,8 +11,6 @@ import static org.mockito.ArgumentMatchers.any;
|
|||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
@ -25,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import net.friedl.fling.model.dto.FlingDto;
|
||||
import net.friedl.fling.model.mapper.FlingMapper;
|
||||
|
@ -42,8 +40,8 @@ public class FlingServiceTest {
|
|||
@Autowired
|
||||
private FlingMapper flingMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageDigest keyHashDigest;
|
||||
@MockBean
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@MockBean
|
||||
private FlingRepository flingRepository;
|
||||
|
@ -62,15 +60,10 @@ public class FlingServiceTest {
|
|||
return new FlingMapperImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageDigest keyHashDigest() throws NoSuchAlgorithmException {
|
||||
return MessageDigest.getInstance("SHA-512");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FlingService flingService(FlingRepository flingRepository, FlingMapper flingMapper,
|
||||
ArchiveService archiveService, MessageDigest keyHashDigest) {
|
||||
return new FlingService(flingRepository, flingMapper, archiveService, keyHashDigest);
|
||||
ArchiveService archiveService, PasswordEncoder passwordEncoder) {
|
||||
return new FlingService(flingRepository, flingMapper, archiveService, passwordEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +72,7 @@ public class FlingServiceTest {
|
|||
this.flingEntity1 = new FlingEntity();
|
||||
flingEntity1.setId(UUID.randomUUID());
|
||||
flingEntity1.setName("fling1");
|
||||
flingEntity1.setAuthCode(new String(Hex.encode(keyHashDigest.digest("authCode1".getBytes()))));
|
||||
flingEntity1.setAuthCode("testhash");
|
||||
flingEntity1.setCreationTime(Instant.now());
|
||||
|
||||
this.flingEntity2 = new FlingEntity();
|
||||
|
@ -94,7 +87,8 @@ public class FlingServiceTest {
|
|||
FlingEntity flingEntity = invocation.getArgument(0);
|
||||
flingEntity.setId(UUID.randomUUID());
|
||||
return flingEntity;
|
||||
}});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -132,10 +126,10 @@ public class FlingServiceTest {
|
|||
FlingDto flingDto = new FlingDto();
|
||||
flingDto.setAuthCode("test");
|
||||
|
||||
String hashedAuthCode = new String(Hex.encode(keyHashDigest.digest(flingDto.getAuthCode().getBytes())));
|
||||
when(passwordEncoder.encode(any(String.class))).thenReturn("testhash");
|
||||
|
||||
FlingDto createdFling = flingService.create(flingDto);
|
||||
assertThat(createdFling.getAuthCode(), is(hashedAuthCode));
|
||||
assertThat(createdFling.getAuthCode(), is("testhash"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -167,6 +161,7 @@ public class FlingServiceTest {
|
|||
@Test
|
||||
public void validateAuthCode_codesMatch_true() {
|
||||
when(flingRepository.getOne(flingEntity1.getId())).thenReturn(flingEntity1);
|
||||
when(passwordEncoder.encode("authCode1")).thenReturn("testhash");
|
||||
|
||||
assertThat(flingService.validateAuthCode(flingEntity1.getId(), "authCode1"), is(true));
|
||||
}
|
||||
|
|
|
@ -162,7 +162,8 @@ public class FileSystemArchiveTest {
|
|||
@Test
|
||||
public void storeArtifact_setsArchivedTrue() throws IOException, URISyntaxException {
|
||||
InputStream artifact2Stream = new FileInputStream(
|
||||
new File(getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI()));
|
||||
new File(
|
||||
getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI()));
|
||||
when(artifactRepository.getOne(artifactEntity2.getId())).thenReturn(artifactEntity2);
|
||||
|
||||
fileSystemArchive.storeArtifact(artifactEntity2.getId(), artifact2Stream);
|
||||
|
@ -175,7 +176,8 @@ public class FileSystemArchiveTest {
|
|||
@Test
|
||||
public void storeArtifact_storesArtifactToFlingDisk() throws URISyntaxException, IOException {
|
||||
InputStream artifact2Stream = new FileInputStream(
|
||||
new File(getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI()));
|
||||
new File(
|
||||
getClass().getClassLoader().getResource("filesystem/artifacts/artifact2").toURI()));
|
||||
when(artifactRepository.getOne(artifactEntity2.getId())).thenReturn(artifactEntity2);
|
||||
|
||||
fileSystemArchive.storeArtifact(artifactEntity2.getId(), artifact2Stream);
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
fling.security.jwt-expiration=18000
|
||||
fling.security.admin-name=admin
|
||||
fling.security.admin-password=123
|
Loading…
Reference in a new issue