diff --git a/format.xml b/format.xml new file mode 100644 index 0000000..2dc0de6 --- /dev/null +++ b/format.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index d0ffe61..f62ddfe 100644 --- a/pom.xml +++ b/pom.xml @@ -134,6 +134,24 @@ + + net.revelc.code.formatter + formatter-maven-plugin + 2.29.0 + + + validate + + format + + + + + format.xml + LF + UTF-8 + + diff --git a/src/main/java/dev/dinauer/metrics/service/Resource.java b/src/main/java/dev/dinauer/metrics/service/Resource.java index 118f7b1..5b1a5c3 100644 --- a/src/main/java/dev/dinauer/metrics/service/Resource.java +++ b/src/main/java/dev/dinauer/metrics/service/Resource.java @@ -1,20 +1,14 @@ package dev.dinauer.metrics.service; -import dev.dinauer.metrics.service.client.AuthenticationService; +import dev.dinauer.metrics.service.client.auth.AuthenticationService; import dev.dinauer.metrics.service.model.BucketUnit; import dev.dinauer.metrics.service.model.Collection; -import io.quarkus.security.UnauthorizedException; import jakarta.inject.Inject; -import jakarta.validation.Valid; -import jakarta.validation.constraints.Size; import jakarta.ws.rs.*; -import org.jboss.logging.Logger; -import javax.swing.text.html.Option; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.OptionalLong; @Path("/{resource}/{metric}") public class Resource diff --git a/src/main/java/dev/dinauer/metrics/service/client/AuthenticationService.java b/src/main/java/dev/dinauer/metrics/service/client/AuthenticationService.java deleted file mode 100644 index 3dfa623..0000000 --- a/src/main/java/dev/dinauer/metrics/service/client/AuthenticationService.java +++ /dev/null @@ -1,113 +0,0 @@ -package dev.dinauer.metrics.service.client; - -import io.quarkus.elytron.security.common.BcryptUtil; -import io.quarkus.security.UnauthorizedException; -import jakarta.enterprise.context.RequestScoped; -import jakarta.inject.Inject; -import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.HttpHeaders; - -import java.util.Base64; -import java.util.List; - -@RequestScoped -public class AuthenticationService -{ - @Context - HttpHeaders headers; - - @Inject - ClientRepo clientRepo; - - public boolean canWrite() - { - AuthHeader authHeader = getAuthHeader(); - switch (authHeader.type()) - { - case AuthType.BASIC -> - { - BasicAuthCredentials credentials = getCredentials(authHeader.credentials()); - Client client = getClient(credentials); - return Permission.RW.equals(client.permission()); - } - } - return false; - } - - public boolean canRead() - { - AuthHeader authHeader = getAuthHeader(); - switch (authHeader.type()) - { - case AuthType.BASIC -> - { - BasicAuthCredentials credentials = getCredentials(authHeader.credentials()); - Client client = getClient(credentials); - return List.of(Permission.RO, Permission.RW).contains(client.permission()); - } - } - return false; - } - - private AuthHeader getAuthHeader() - { - String authHeader = headers.getHeaderString("Authorization"); - if (authHeader != null) - { - String[] sections = authHeader.split("\\s+"); - if (sections.length == 2) - { - try - { - return new AuthHeader(AuthType.valueOf(sections[0].toUpperCase()), sections[1]); - } - catch (IllegalArgumentException e) - { - throw new UnauthorizedException(); - } - } - } - throw new UnauthorizedException(); - } - - private BasicAuthCredentials getCredentials(String base64Credentials) - { - String credentials = new String(Base64.getDecoder().decode(base64Credentials)); - String[] credentialSections = credentials.split(":"); - if (credentialSections.length == 2) - { - String clientId = credentialSections[0]; - String password = credentialSections[1]; - return new BasicAuthCredentials(clientId, password); - } - throw new UnauthorizedException(); - } - - private Client getClient(BasicAuthCredentials credentials) - { - Client client = clientRepo.findById(credentials.clientId()); - if (client != null) - { - if (BcryptUtil.matches(credentials.password(), client.password())) - { - return client; - } - throw new UnauthorizedException(); - } - throw new NotFoundException(); - } - - private record AuthHeader(AuthType type, String credentials) - { - } - - private enum AuthType - { - BASIC, BEARER - } - - private record BasicAuthCredentials(String clientId, String password) - { - } -} diff --git a/src/main/java/dev/dinauer/metrics/service/client/Client.java b/src/main/java/dev/dinauer/metrics/service/client/Client.java index 29a72fc..0521b76 100644 --- a/src/main/java/dev/dinauer/metrics/service/client/Client.java +++ b/src/main/java/dev/dinauer/metrics/service/client/Client.java @@ -2,8 +2,7 @@ package dev.dinauer.metrics.service.client; import java.util.Objects; -public record Client(String id, String password, Permission permission) -{ +public record Client(String id, String password, Permission permission) { @Override public boolean equals(Object object) { diff --git a/src/main/java/dev/dinauer/metrics/service/client/ClientRepo.java b/src/main/java/dev/dinauer/metrics/service/client/ClientRepo.java index 75cc25e..3d731b3 100644 --- a/src/main/java/dev/dinauer/metrics/service/client/ClientRepo.java +++ b/src/main/java/dev/dinauer/metrics/service/client/ClientRepo.java @@ -13,7 +13,7 @@ import java.util.List; @ApplicationScoped public class ClientRepo { - private final static String CLIENT_PROPERTY_PREFIX= "dev.dinauer.metrics-service.client"; + private final static String CLIENT_PROPERTY_PREFIX = "dev.dinauer.metrics-service.client"; List clients; diff --git a/src/main/java/dev/dinauer/metrics/service/client/Permission.java b/src/main/java/dev/dinauer/metrics/service/client/Permission.java index 50dc57e..4f68a6e 100644 --- a/src/main/java/dev/dinauer/metrics/service/client/Permission.java +++ b/src/main/java/dev/dinauer/metrics/service/client/Permission.java @@ -2,5 +2,5 @@ package dev.dinauer.metrics.service.client; public enum Permission { - RW, RO + RW, RO, WO } diff --git a/src/main/java/dev/dinauer/metrics/service/client/auth/AuthenticationService.java b/src/main/java/dev/dinauer/metrics/service/client/auth/AuthenticationService.java new file mode 100644 index 0000000..a23bb5e --- /dev/null +++ b/src/main/java/dev/dinauer/metrics/service/client/auth/AuthenticationService.java @@ -0,0 +1,74 @@ +package dev.dinauer.metrics.service.client.auth; + +import dev.dinauer.metrics.service.client.Client; +import dev.dinauer.metrics.service.client.Permission; +import dev.dinauer.metrics.service.client.auth.utils.AuthHeader; +import dev.dinauer.metrics.service.client.auth.utils.AuthType; +import io.quarkus.security.UnauthorizedException; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; + +import java.util.List; + +@RequestScoped +public class AuthenticationService +{ + private static final List READ_PERMISSIONS = List.of(Permission.RO, Permission.RW); + private static final List WRITE_PERMISSIONS = List.of(Permission.WO, Permission.RW); + + @Context + HttpHeaders headers; + + @Inject + BasicAuthClientProvider basicAuthClientProvider; + + @Inject + BearerAuthClientProvider bearerAuthClientProvider; + + public boolean canWrite() + { + return WRITE_PERMISSIONS.contains(getClient().permission()); + } + + public boolean canRead() + { + return READ_PERMISSIONS.contains(getClient().permission()); + } + + private Client getClient() + { + AuthHeader header = getAuthHeader(); + switch (header.type()) + { + case BASIC -> { + return basicAuthClientProvider.get(header.credentials()); + } + case BEARER -> { + return bearerAuthClientProvider.get(header.credentials()); + } + } + throw new UnauthorizedException(); + } + + private AuthHeader getAuthHeader() + { + String authHeader = headers.getHeaderString("Authorization"); + if (authHeader != null) + { + String[] sections = authHeader.split("\\s+"); + if (sections.length == 2) + { + try + { + return new AuthHeader(AuthType.valueOf(sections[0].toUpperCase()), sections[1]); + } catch (IllegalArgumentException e) + { + throw new UnauthorizedException(); + } + } + } + throw new UnauthorizedException(); + } +} diff --git a/src/main/java/dev/dinauer/metrics/service/client/auth/BasicAuthClientProvider.java b/src/main/java/dev/dinauer/metrics/service/client/auth/BasicAuthClientProvider.java new file mode 100644 index 0000000..efc9892 --- /dev/null +++ b/src/main/java/dev/dinauer/metrics/service/client/auth/BasicAuthClientProvider.java @@ -0,0 +1,47 @@ +package dev.dinauer.metrics.service.client.auth; + +import dev.dinauer.metrics.service.client.Client; +import dev.dinauer.metrics.service.client.ClientRepo; +import dev.dinauer.metrics.service.client.auth.utils.BasicAuthCredentials; +import io.quarkus.elytron.security.common.BcryptUtil; +import io.quarkus.security.UnauthorizedException; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.NotFoundException; + +import java.util.Base64; + +@ApplicationScoped +public class BasicAuthClientProvider +{ + @Inject + ClientRepo clientRepo; + + public Client get(String credentials) + { + BasicAuthCredentials basicAuthCredentials = getCredentials(credentials); + Client client = clientRepo.findById(basicAuthCredentials.clientId()); + if (client != null) + { + if (BcryptUtil.matches(basicAuthCredentials.password(), client.password())) + { + return client; + } + throw new UnauthorizedException(); + } + throw new NotFoundException(); + } + + private BasicAuthCredentials getCredentials(String base64Credentials) + { + String credentials = new String(Base64.getDecoder().decode(base64Credentials)); + String[] credentialSections = credentials.split(":"); + if (credentialSections.length == 2) + { + String clientId = credentialSections[0]; + String password = credentialSections[1]; + return new BasicAuthCredentials(clientId, password); + } + throw new UnauthorizedException(); + } +} diff --git a/src/main/java/dev/dinauer/metrics/service/client/auth/BearerAuthClientProvider.java b/src/main/java/dev/dinauer/metrics/service/client/auth/BearerAuthClientProvider.java new file mode 100644 index 0000000..1d08bc6 --- /dev/null +++ b/src/main/java/dev/dinauer/metrics/service/client/auth/BearerAuthClientProvider.java @@ -0,0 +1,44 @@ +package dev.dinauer.metrics.service.client.auth; + +import dev.dinauer.metrics.service.client.Client; +import dev.dinauer.metrics.service.client.Permission; +import io.quarkus.security.UnauthorizedException; +import io.smallrye.jwt.auth.principal.JWTParser; +import io.smallrye.jwt.auth.principal.ParseException; +import io.smallrye.jwt.build.Jwt; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.jwt.JsonWebToken; + +import java.util.Optional; + +@ApplicationScoped +public class BearerAuthClientProvider +{ + @Inject + JWTParser parser; + + @ConfigProperty(name = "dev.dinauer.metrics-service.jwt.client.field") + String clientIdField; + + public Client get(String credentials) + { + try + { + JsonWebToken token = parser.parse(credentials); + if (token != null) + { + Optional user = token.claim(clientIdField); + if (user.isPresent()) + { + return new Client(user.get(), null, Permission.RO); + } + } + throw new UnauthorizedException(); + } catch (ParseException e) + { + throw new UnauthorizedException(); + } + } +} diff --git a/src/main/java/dev/dinauer/metrics/service/client/auth/utils/AuthHeader.java b/src/main/java/dev/dinauer/metrics/service/client/auth/utils/AuthHeader.java new file mode 100644 index 0000000..270e468 --- /dev/null +++ b/src/main/java/dev/dinauer/metrics/service/client/auth/utils/AuthHeader.java @@ -0,0 +1,4 @@ +package dev.dinauer.metrics.service.client.auth.utils; + +public record AuthHeader(AuthType type, String credentials) { +} \ No newline at end of file diff --git a/src/main/java/dev/dinauer/metrics/service/client/auth/utils/AuthType.java b/src/main/java/dev/dinauer/metrics/service/client/auth/utils/AuthType.java new file mode 100644 index 0000000..10ab907 --- /dev/null +++ b/src/main/java/dev/dinauer/metrics/service/client/auth/utils/AuthType.java @@ -0,0 +1,6 @@ +package dev.dinauer.metrics.service.client.auth.utils; + +public enum AuthType +{ + BASIC, BEARER +} \ No newline at end of file diff --git a/src/main/java/dev/dinauer/metrics/service/client/auth/utils/BasicAuthCredentials.java b/src/main/java/dev/dinauer/metrics/service/client/auth/utils/BasicAuthCredentials.java new file mode 100644 index 0000000..79cb002 --- /dev/null +++ b/src/main/java/dev/dinauer/metrics/service/client/auth/utils/BasicAuthCredentials.java @@ -0,0 +1,4 @@ +package dev.dinauer.metrics.service.client.auth.utils; + +public record BasicAuthCredentials(String clientId, String password) { +} diff --git a/src/main/java/dev/dinauer/metrics/service/model/Collection.java b/src/main/java/dev/dinauer/metrics/service/model/Collection.java index 28f0ea4..085d372 100644 --- a/src/main/java/dev/dinauer/metrics/service/model/Collection.java +++ b/src/main/java/dev/dinauer/metrics/service/model/Collection.java @@ -84,8 +84,7 @@ public class Collection try { this.metrics = OBJECT_MAPPER.writeValueAsString(metrics); - } - catch (JsonProcessingException e) + } catch (JsonProcessingException e) { throw new RuntimeException(e); } @@ -124,9 +123,10 @@ public class Collection { try { - return OBJECT_MAPPER.readValue(metrics, new TypeReference>() {}); - } - catch (JsonProcessingException e) + return OBJECT_MAPPER.readValue(metrics, new TypeReference>() + { + }); + } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/src/main/java/dev/dinauer/metrics/service/utils/TimestampGenerator.java b/src/main/java/dev/dinauer/metrics/service/utils/TimestampGenerator.java index 4484920..d0e0ea7 100644 --- a/src/main/java/dev/dinauer/metrics/service/utils/TimestampGenerator.java +++ b/src/main/java/dev/dinauer/metrics/service/utils/TimestampGenerator.java @@ -14,19 +14,19 @@ public class TimestampGenerator { switch (unit) { - case BucketUnit.RAW -> + case BucketUnit.RAW: { return timestamp.format(DateTimeFormatter.ISO_DATE_TIME).substring(0, 19); } - case BucketUnit.HOURLY -> + case BucketUnit.HOURLY: { return timestamp.format(DateTimeFormatter.ISO_DATE_TIME).substring(0, 13); } - case BucketUnit.DAILY -> + case BucketUnit.DAILY: { return timestamp.format(DateTimeFormatter.ISO_DATE_TIME).substring(0, 10); } - case BucketUnit.WEEKLY -> + case BucketUnit.WEEKLY: { String week = String.valueOf(timestamp.get(WeekFields.ISO.weekOfWeekBasedYear())); String year = String.valueOf(timestamp.get(WeekFields.ISO.weekBasedYear())); @@ -35,15 +35,15 @@ public class TimestampGenerator String yearString = "0".repeat(4 - year.length()).concat(year); return String.format("%s-W%s", yearString, weekString); } - case BucketUnit.MONTHLY -> + case BucketUnit.MONTHLY: { return timestamp.format(DateTimeFormatter.ISO_DATE_TIME).substring(0, 7); } - case BucketUnit.YEARLY -> + case BucketUnit.YEARLY: { return timestamp.format(DateTimeFormatter.ISO_DATE_TIME).substring(0, 4); } - case BucketUnit.TOTAL -> + case BucketUnit.TOTAL: { return "TOTAL"; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9446478..8247b1e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,6 +4,7 @@ quarkus.http.root-path=/api/metrics dev.dinauer.metrics-service.buckets=RAW,HOURLY,DAILY,WEEKLY,MONTHLY,YEARLY,TOTAL dev.dinauer.metrics-service.client.kubooboo.ro=3749832748923748923 +dev.dinauer.metrics-service.jwt.client.field=upn # JWT mp.jwt.verify.publickey.location=dev/publicKey.pem diff --git a/src/test/java/dev/dinauer/GreetingResourceIT.java b/src/test/java/dev/dinauer/GreetingResourceIT.java index 64cdf51..9050ab2 100644 --- a/src/test/java/dev/dinauer/GreetingResourceIT.java +++ b/src/test/java/dev/dinauer/GreetingResourceIT.java @@ -3,6 +3,7 @@ package dev.dinauer; import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest -class GreetingResourceIT extends GreetingResourceTest { +class GreetingResourceIT extends GreetingResourceTest +{ // Execute the same tests but in packaged mode. } diff --git a/src/test/java/dev/dinauer/GreetingResourceTest.java b/src/test/java/dev/dinauer/GreetingResourceTest.java index 7e0d388..914a42f 100644 --- a/src/test/java/dev/dinauer/GreetingResourceTest.java +++ b/src/test/java/dev/dinauer/GreetingResourceTest.java @@ -7,14 +7,12 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; @QuarkusTest -class GreetingResourceTest { +class GreetingResourceTest +{ @Test - void testHelloEndpoint() { - given() - .when().get("/hello") - .then() - .statusCode(200) - .body(is("Hello from Quarkus REST")); + void testHelloEndpoint() + { + given().when().get("/hello").then().statusCode(200).body(is("Hello from Quarkus REST")); } } \ No newline at end of file