forked from quarkus-qe/quarkus-test-suite
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create security/webauthn module with reactive mysql client
create quarkus webauthn code with reactive mysql client and compose yaml do some tweaks and add init.sql delete files not needed in the module for quarkus test suite delete docker files not needed in the module for quarkus test suite modify Readme fix some annotation and create src/test add security/webauthn module add LoginResource class remove init.sql and modify mysql.properties create WebAuthnCredential method to associate with the User add method to associate with WebAuthnCredential to the new User add logOut tests variable more descriptive use vertx HttpClient for testing purposes and rename of xc5 variables remove when from rest assured and add indentation delete uncecessary dependencies register a User with VirtualAuthenticator and selenium add dependencies needed for selenium and also jupiter junit add AbstractWebAuthnPlaywright example add WebDriverWait wait variable fix pom conflicts close http client and vertx to prevent leaks use MySqlService parameters withProperties create basic tests on AbstractWebAuthnTest remove classes not needed for now add failed testRegisterSameUserShouldNotAllowed change assertThat on testRegisterSameUserShouldNotAllowed add AdminResource add properties as static final and remove restAssured check from setUp use upstream mysql80 image call onSuccess after http.requests add MyWebAuthnHardware class tweak logic with all methods and parameters needed to register a user add methods invokeRegistration and invokeCallback needed in the user registration with mysql.80.image tests works add openshift tests add logoutUser method tweak to new user credential persist add security/webauthn to README refactor name method remove UTF8 propery drom mysql.properties add cbor library add admin checks use the getApp().getHost() instead of localhost improve security/webauthn description in README add some method order and another tweaks drop jackson-cbor version change deprecate getApp().getHost() method rename class to OpenShiftWebAuthnIT to be included according maven pattern configuration change expectedLog change localhost rpDomain to app.getUri() remove drop and create from mysql.properties add properties quarkus.build.skip fix regex header because failed on openshift add TODO on OpenShift scenario with issue quarkus-qe#1500 disabled OpenShiftScenario and call to ADMIN endpoint to get 404 status move the tests to the package io.quarkus.ts.security.webauthn
- Loading branch information
1 parent
3ef90ed
commit 0bb0cbc
Showing
19 changed files
with
1,215 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>io.quarkus.ts.qe</groupId> | ||
<artifactId>parent</artifactId> | ||
<version>1.0.0-SNAPSHOT</version> | ||
<relativePath>../..</relativePath> | ||
</parent> | ||
<artifactId>security-webauthn</artifactId> | ||
<version>1.0.0-SNAPSHOT</version> | ||
<name>Quarkus QE TS: Security: WebAuth</name> | ||
<properties> | ||
<quarkus.build.skip>true</quarkus.build.skip> | ||
</properties> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-resteasy-reactive</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-reactive-mysql-client</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-security-webauthn</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-hibernate-reactive-panache</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-junit5</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus.qe</groupId> | ||
<artifactId>quarkus-test-service-database</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.dataformat</groupId> | ||
<artifactId>jackson-dataformat-cbor</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
18 changes: 18 additions & 0 deletions
18
security/webauthn/src/main/java/io/quarkus/ts/security/webauthn/api/AdminResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package io.quarkus.ts.security.webauthn.api; | ||
|
||
import jakarta.annotation.security.RolesAllowed; | ||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.Produces; | ||
import jakarta.ws.rs.core.MediaType; | ||
|
||
@Path("/api/admin") | ||
public class AdminResource { | ||
|
||
@GET | ||
@RolesAllowed("admin") | ||
@Produces(MediaType.TEXT_PLAIN) | ||
public String adminResource() { | ||
return "admin"; | ||
} | ||
} |
110 changes: 110 additions & 0 deletions
110
security/webauthn/src/main/java/io/quarkus/ts/security/webauthn/api/LoginResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package io.quarkus.ts.security.webauthn.api; | ||
|
||
import jakarta.inject.Inject; | ||
import jakarta.ws.rs.BeanParam; | ||
import jakarta.ws.rs.POST; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.core.Response; | ||
|
||
import org.jboss.resteasy.reactive.RestForm; | ||
|
||
import io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactional; | ||
import io.quarkus.security.webauthn.WebAuthnLoginResponse; | ||
import io.quarkus.security.webauthn.WebAuthnRegisterResponse; | ||
import io.quarkus.security.webauthn.WebAuthnSecurity; | ||
import io.quarkus.ts.security.webauthn.model.User; | ||
import io.quarkus.ts.security.webauthn.model.WebAuthnCredential; | ||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.ext.auth.webauthn.Authenticator; | ||
import io.vertx.ext.web.RoutingContext; | ||
|
||
@Path("") | ||
public class LoginResource { | ||
|
||
@Inject | ||
WebAuthnSecurity webAuthnSecurity; | ||
|
||
@Path("/login") | ||
@POST | ||
@ReactiveTransactional | ||
public Uni<Response> login(@RestForm String userName, | ||
@BeanParam WebAuthnLoginResponse webAuthnResponse, | ||
RoutingContext ctx) { | ||
// Input validation | ||
if (userName == null || userName.isEmpty() | ||
|| !webAuthnResponse.isSet() | ||
|| !webAuthnResponse.isValid()) { | ||
return Uni.createFrom().item(Response.status(Response.Status.BAD_REQUEST).build()); | ||
} | ||
|
||
Uni<User> userUni = User.findByUserName(userName); | ||
return userUni.flatMap(user -> { | ||
if (user == null) { | ||
// Invalid user | ||
return Uni.createFrom().item(Response.status(Response.Status.BAD_REQUEST).build()); | ||
} | ||
Uni<Authenticator> authenticator = this.webAuthnSecurity.login(webAuthnResponse, ctx); | ||
|
||
return authenticator | ||
// bump the auth counter | ||
.invoke(auth -> user.webAuthnCredential.counter = auth.getCounter()) | ||
.map(auth -> { | ||
// make a login cookie | ||
this.webAuthnSecurity.rememberUser(auth.getUserName(), ctx); | ||
return Response.ok().build(); | ||
}) | ||
// handle login failure | ||
.onFailure().recoverWithItem(x -> { | ||
// make a proper error response | ||
return Response.status(Response.Status.BAD_REQUEST).build(); | ||
}); | ||
|
||
}); | ||
} | ||
|
||
@Path("/register") | ||
@POST | ||
@ReactiveTransactional | ||
public Uni<Response> register(@RestForm String userName, | ||
@BeanParam WebAuthnRegisterResponse webAuthnResponse, | ||
RoutingContext ctx) { | ||
// Input validation | ||
if (userName == null || userName.isEmpty() | ||
|| !webAuthnResponse.isSet() | ||
|| !webAuthnResponse.isValid()) { | ||
return Uni.createFrom().item(Response.status(Response.Status.BAD_REQUEST).build()); | ||
} | ||
|
||
Uni<User> userUni = User.findByUserName(userName); | ||
return userUni.flatMap(user -> { | ||
if (user != null) { | ||
// Duplicate user | ||
return Uni.createFrom().item(Response.status(Response.Status.BAD_REQUEST).build()); | ||
} | ||
Uni<Authenticator> authenticator = this.webAuthnSecurity.register(webAuthnResponse, ctx); | ||
|
||
return authenticator | ||
// store the user | ||
.flatMap(auth -> { | ||
User newUser = new User(); | ||
newUser.userName = auth.getUserName(); | ||
WebAuthnCredential credential = new WebAuthnCredential(auth, newUser); | ||
return credential.persist() | ||
.flatMap(c -> newUser.<User> persist()); | ||
|
||
}) | ||
.map(newUser -> { | ||
// make a login cookie | ||
this.webAuthnSecurity.rememberUser(newUser.userName, ctx); | ||
return Response.ok().build(); | ||
}) | ||
// handle login failure | ||
.onFailure().recoverWithItem(x -> { | ||
// make a proper error response | ||
return Response.status(Response.Status.BAD_REQUEST).build(); | ||
}); | ||
|
||
}); | ||
} | ||
|
||
} |
27 changes: 27 additions & 0 deletions
27
security/webauthn/src/main/java/io/quarkus/ts/security/webauthn/api/PublicResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package io.quarkus.ts.security.webauthn.api; | ||
|
||
import java.security.Principal; | ||
|
||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.Produces; | ||
import jakarta.ws.rs.core.Context; | ||
import jakarta.ws.rs.core.MediaType; | ||
import jakarta.ws.rs.core.SecurityContext; | ||
|
||
@Path("/api/public") | ||
public class PublicResource { | ||
@GET | ||
@Produces(MediaType.TEXT_PLAIN) | ||
public String publicResource() { | ||
return "public"; | ||
} | ||
|
||
@GET | ||
@Path("/me") | ||
@Produces(MediaType.TEXT_PLAIN) | ||
public String me(@Context SecurityContext securityContext) { | ||
Principal user = securityContext.getUserPrincipal(); | ||
return user != null ? user.getName() : "<not logged in>"; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
security/webauthn/src/main/java/io/quarkus/ts/security/webauthn/api/UserResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package io.quarkus.ts.security.webauthn.api; | ||
|
||
import jakarta.annotation.security.RolesAllowed; | ||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.core.Context; | ||
import jakarta.ws.rs.core.SecurityContext; | ||
|
||
@Path("/api/users") | ||
public class UserResource { | ||
@GET | ||
@RolesAllowed("user") | ||
@Path("/me") | ||
public String me(@Context SecurityContext securityContext) { | ||
return securityContext.getUserPrincipal().getName(); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
security/webauthn/src/main/java/io/quarkus/ts/security/webauthn/model/User.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package io.quarkus.ts.security.webauthn.model; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.OneToOne; | ||
import jakarta.persistence.Table; | ||
|
||
import io.quarkus.hibernate.reactive.panache.PanacheEntity; | ||
import io.smallrye.mutiny.Uni; | ||
|
||
@Table(name = "user_table") | ||
@Entity | ||
public class User extends PanacheEntity { | ||
|
||
@Column(unique = true) | ||
public String userName; | ||
|
||
@OneToOne(mappedBy = "user") | ||
public WebAuthnCredential webAuthnCredential; | ||
|
||
public static Uni<User> findByUserName(String userName) { | ||
return find("userName", userName).firstResult(); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...ity/webauthn/src/main/java/io/quarkus/ts/security/webauthn/model/WebAuthnCertificate.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package io.quarkus.ts.security.webauthn.model; | ||
|
||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.ManyToOne; | ||
|
||
import io.quarkus.hibernate.reactive.panache.PanacheEntity; | ||
|
||
@Entity | ||
public class WebAuthnCertificate extends PanacheEntity { | ||
@ManyToOne | ||
public WebAuthnCredential webAuthnCredential; | ||
|
||
/** | ||
* The list of X509 certificates encoded as base64url. | ||
*/ | ||
public String base64X509Certificate; | ||
} |
121 changes: 121 additions & 0 deletions
121
...rity/webauthn/src/main/java/io/quarkus/ts/security/webauthn/model/WebAuthnCredential.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package io.quarkus.ts.security.webauthn.model; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.OneToMany; | ||
import jakarta.persistence.OneToOne; | ||
import jakarta.persistence.Table; | ||
import jakarta.persistence.UniqueConstraint; | ||
|
||
import io.quarkus.hibernate.reactive.panache.PanacheEntity; | ||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.ext.auth.webauthn.Authenticator; | ||
import io.vertx.ext.auth.webauthn.PublicKeyCredential; | ||
|
||
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "userName", "credID" })) | ||
@Entity | ||
public class WebAuthnCredential extends PanacheEntity { | ||
/** | ||
* The username linked to this authenticator | ||
*/ | ||
public String userName; | ||
|
||
/** | ||
* The type of key (must be "public-key") | ||
*/ | ||
public String type = "public-key"; | ||
|
||
/** | ||
* The non user identifiable id for the authenticator | ||
*/ | ||
public String credID; | ||
|
||
/** | ||
* The public key associated with this authenticator | ||
*/ | ||
public String publicKey; | ||
|
||
/** | ||
* The signature counter of the authenticator to prevent replay attacks | ||
*/ | ||
public long counter; | ||
|
||
public String aaguid; | ||
|
||
/** | ||
* The Authenticator attestation certificates object, a JSON like: | ||
* | ||
* <pre>{@code | ||
* { | ||
* "alg": "string", | ||
* "x5c": [ | ||
* "base64" | ||
* ] | ||
* } | ||
* }</pre> | ||
*/ | ||
/** | ||
* The algorithm used for the public credential | ||
*/ | ||
public PublicKeyCredential alg; | ||
|
||
/** | ||
* The list of X509 certificates encoded as base64url. | ||
*/ | ||
@OneToMany(mappedBy = "webAuthnCredential") | ||
public List<WebAuthnCertificate> webAuthnx509Certificates = new ArrayList<>(); | ||
|
||
public String fmt; | ||
|
||
// owning side | ||
@OneToOne | ||
public User user; | ||
|
||
public WebAuthnCredential() { | ||
} | ||
|
||
public WebAuthnCredential(Authenticator authenticator, User user) { | ||
aaguid = authenticator.getAaguid(); | ||
if (authenticator.getAttestationCertificates() != null) | ||
alg = authenticator.getAttestationCertificates().getAlg(); | ||
counter = authenticator.getCounter(); | ||
credID = authenticator.getCredID(); | ||
fmt = authenticator.getFmt(); | ||
publicKey = authenticator.getPublicKey(); | ||
type = authenticator.getType(); | ||
userName = authenticator.getUserName(); | ||
if (authenticator.getAttestationCertificates() != null | ||
&& authenticator.getAttestationCertificates().getX5c() != null) { | ||
for (String x509VCertificate : authenticator.getAttestationCertificates().getX5c()) { | ||
WebAuthnCertificate cert = new WebAuthnCertificate(); | ||
cert.base64X509Certificate = x509VCertificate; | ||
cert.webAuthnCredential = this; | ||
this.webAuthnx509Certificates.add(cert); | ||
} | ||
} | ||
this.user = user; | ||
user.webAuthnCredential = this; | ||
} | ||
|
||
public static Uni<WebAuthnCredential> createWebAuthnCredential(Authenticator authenticator, User user) { | ||
WebAuthnCredential credential = new WebAuthnCredential(authenticator, user); | ||
credential.persistAndFlush(); | ||
user.webAuthnCredential = credential; | ||
user.persistAndFlush(); | ||
return Uni.createFrom().item(credential); | ||
} | ||
|
||
public static Uni<List<WebAuthnCredential>> findByUserName(String userName) { | ||
return list("userName", userName); | ||
} | ||
|
||
public static Uni<List<WebAuthnCredential>> findByCredID(String credID) { | ||
return list("credID", credID); | ||
} | ||
|
||
public <T> Uni<T> fetch(T association) { | ||
return getSession().flatMap(session -> session.fetch(association)); | ||
} | ||
} |
Oops, something went wrong.