split client and server into maven modules

This commit is contained in:
Athou
2022-08-13 10:34:59 +02:00
parent 4c4868a2b6
commit ac7b6eeb21
277 changed files with 645 additions and 521 deletions

View File

@@ -0,0 +1,59 @@
package com.commafeed.frontend.auth;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.StringUtils;
import org.passay.CharacterRule;
import org.passay.EnglishCharacterData;
import org.passay.LengthRule;
import org.passay.PasswordData;
import org.passay.PasswordValidator;
import org.passay.RuleResult;
import org.passay.WhitespaceRule;
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
@Override
public void initialize(ValidPassword constraintAnnotation) {
// nothing to do
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (StringUtils.isBlank(value)) {
return true;
}
PasswordValidator validator = buildPasswordValidator();
RuleResult result = validator.validate(new PasswordData(value));
if (result.isValid()) {
return true;
}
List<String> messages = validator.getMessages(result);
String message = String.join(System.lineSeparator(), messages);
context.buildConstraintViolationWithTemplate(message).addConstraintViolation().disableDefaultConstraintViolation();
return false;
}
private PasswordValidator buildPasswordValidator() {
return new PasswordValidator(
// length
new LengthRule(8, 128),
// 1 uppercase char
new CharacterRule(EnglishCharacterData.UpperCase, 1),
// 1 lowercase char
new CharacterRule(EnglishCharacterData.LowerCase, 1),
// 1 digit
new CharacterRule(EnglishCharacterData.Digit, 1),
// 1 special char
new CharacterRule(EnglishCharacterData.Special, 1),
// no whitespace
new WhitespaceRule());
}
}

View File

@@ -0,0 +1,22 @@
package com.commafeed.frontend.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.commafeed.backend.model.UserRole.Role;
@Inherited
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface SecurityCheck {
/**
* Roles needed.
*/
Role value() default Role.USER;
boolean apiKeyAllowed() default false;
}

View File

@@ -0,0 +1,99 @@
package com.commafeed.frontend.auth;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.ContainerRequest;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.service.UserService;
import com.commafeed.frontend.session.SessionHelper;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class SecurityCheckFactory implements Function<ContainerRequest, User> {
private static final String PREFIX = "Basic";
private final UserService userService;
private final HttpServletRequest request;
private final Role role;
private final boolean apiKeyAllowed;
@Override
public User apply(ContainerRequest req) {
Optional<User> user = apiKeyLogin();
if (!user.isPresent()) {
user = basicAuthenticationLogin();
}
if (!user.isPresent()) {
user = cookieSessionLogin(new SessionHelper(request));
}
if (user.isPresent()) {
Set<Role> roles = userService.getRoles(user.get());
if (roles.contains(role)) {
return user.get();
} else {
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You don't have the required role to access this resource.")
.type(MediaType.TEXT_PLAIN_TYPE)
.build());
}
} else {
throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED)
.entity("Credentials are required to access this resource.")
.type(MediaType.TEXT_PLAIN_TYPE)
.build());
}
}
Optional<User> cookieSessionLogin(SessionHelper sessionHelper) {
Optional<User> loggedInUser = sessionHelper.getLoggedInUser();
if (loggedInUser.isPresent()) {
userService.performPostLoginActivities(loggedInUser.get());
}
return loggedInUser;
}
private Optional<User> basicAuthenticationLogin() {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null) {
int space = header.indexOf(' ');
if (space > 0) {
String method = header.substring(0, space);
if (PREFIX.equalsIgnoreCase(method)) {
byte[] decodedBytes = Base64.getDecoder().decode(header.substring(space + 1));
String decoded = new String(decodedBytes, StandardCharsets.ISO_8859_1);
int i = decoded.indexOf(':');
if (i > 0) {
String username = decoded.substring(0, i);
String password = decoded.substring(i + 1);
return userService.login(username, password);
}
}
}
}
return Optional.empty();
}
private Optional<User> apiKeyLogin() {
String apiKey = request.getParameter("apiKey");
if (apiKey != null && apiKeyAllowed) {
return userService.login(apiKey);
}
return Optional.empty();
}
}

View File

@@ -0,0 +1,63 @@
package com.commafeed.frontend.auth;
import java.util.function.Function;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.internal.inject.AbstractValueParamProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
import com.commafeed.backend.model.User;
import com.commafeed.backend.service.UserService;
import lombok.RequiredArgsConstructor;
@Singleton
public class SecurityCheckFactoryProvider extends AbstractValueParamProvider {
private UserService userService;
private HttpServletRequest request;
@Inject
public SecurityCheckFactoryProvider(final MultivaluedParameterExtractorProvider extractorProvider, UserService userService,
HttpServletRequest request) {
super(() -> extractorProvider, Parameter.Source.UNKNOWN);
this.userService = userService;
this.request = request;
}
@Override
protected Function<ContainerRequest, ?> createValueProvider(Parameter parameter) {
final Class<?> classType = parameter.getRawType();
SecurityCheck securityCheck = parameter.getAnnotation(SecurityCheck.class);
if (securityCheck == null) {
return null;
}
if (!classType.isAssignableFrom(User.class)) {
return null;
}
return new SecurityCheckFactory(userService, request, securityCheck.value(), securityCheck.apiKeyAllowed());
}
@RequiredArgsConstructor
public static class Binder extends AbstractBinder {
private final UserService userService;
@Override
protected void configure() {
bind(SecurityCheckFactoryProvider.class).to(ValueParamProvider.class).in(Singleton.class);
bind(userService).to(UserService.class);
}
}
}

View File

@@ -0,0 +1,23 @@
package com.commafeed.frontend.auth;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Documented
@Constraint(validatedBy = PasswordConstraintValidator.class)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
String message() default "Invalid Password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}