Skip to content
Snippets Groups Projects
Verified Commit 1baec2c8 authored by Ira ¯\_(ツ)_/¯'s avatar Ira ¯\_(ツ)_/¯
Browse files

Fix formatting and linting (google style checks)

parent 355d9c28
No related branches found
No related tags found
No related merge requests found
Showing with 203 additions and 145 deletions
package org.archlinux.keycloak.mailpass.rest; package org.archlinux.keycloak.mailpass.rest;
import static io.restassured.RestAssured.given;
import static org.junit.Assert.assertEquals;
import com.palantir.docker.compose.DockerComposeRule; import com.palantir.docker.compose.DockerComposeRule;
import com.palantir.docker.compose.connection.waiting.HealthChecks; import com.palantir.docker.compose.connection.waiting.HealthChecks;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.response.Response; import io.restassured.response.Response;
import static io.restassured.RestAssured.given;
import static org.junit.Assert.assertEquals;
import java.text.MessageFormat; import java.text.MessageFormat;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
public class MailPassResourceIntegrationTest { public class MailPassResourceIntegrationTest {
private final static String ROOT_URI = "http://localhost:{0,number,#}/auth/realms/master/mailpass/roleauth"; private static final String ROOT_URI =
private final static String TOKEN_GEN_URI = "http://localhost:{0,number,#}/auth/realms/master/protocol/openid-connect/token"; "http://localhost:{0,number,#}/auth/realms/master/mailpass/roleauth";
private static final String TOKEN_GEN_URI =
"http://localhost:{0,number,#}/auth/realms/master/protocol/openid-connect/token";
private static String adminToken; private static String adminToken;
private static int port; private static int port;
@ClassRule @ClassRule
public static DockerComposeRule docker = DockerComposeRule.builder() public static DockerComposeRule docker =
.file("src/integrationTest/resources/docker-compose.yml").waitingForService("keycloak", HealthChecks DockerComposeRule.builder()
.toRespondOverHttp(8080, (port) -> port.inFormat("http://$HOST:$EXTERNAL_PORT/auth/realms/master"))) .file("src/integrationTest/resources/docker-compose.yml")
.build(); .waitingForService(
"keycloak",
HealthChecks.toRespondOverHttp(
8080, (port) -> port.inFormat("http://$HOST:$EXTERNAL_PORT/auth/realms/master")))
.build();
/** Generate bearer token and make it available to all the tests in the class. */
@BeforeClass @BeforeClass
public static void setup() { public static void setup() {
port = docker.containers().container("keycloak").port(8080).getExternalPort(); port = docker.containers().container("keycloak").port(8080).getExternalPort();
Response response = given().accept(ContentType.JSON).contentType(ContentType.URLENC) Response response =
.body("grant_type=password&username=admin&password=admin&client_id=admin-cli") given()
.post(MessageFormat.format(TOKEN_GEN_URI, port)); .accept(ContentType.JSON)
.contentType(ContentType.URLENC)
.body("grant_type=password&username=admin&password=admin&client_id=admin-cli")
.post(MessageFormat.format(TOKEN_GEN_URI, port));
adminToken = response.jsonPath().getString("access_token"); adminToken = response.jsonPath().getString("access_token");
} }
@Test @Test
public void testNonAuthenticatedAccessReturnsUnauthorized401() { public void testNonAuthenticatedAccessReturnsUnauthorized401() {
Response response = given().contentType(ContentType.JSON).body("{ \"password\": \"password\" }").when() Response response =
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); given()
.contentType(ContentType.JSON)
.body("{ \"password\": \"password\" }")
.when()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
assertEquals(401, response.getStatusCode()); assertEquals(401, response.getStatusCode());
} }
@Test @Test
public void testAuthenticatedAccessReturnsCreated200() { public void testAuthenticatedAccessReturnsCreated200() {
Response response = given().header("Authorization", "Bearer " + adminToken).contentType(ContentType.JSON) Response response =
.body("{ \"password\": \"password\" }").when() given()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); .header("Authorization", "Bearer " + adminToken)
.contentType(ContentType.JSON)
.body("{ \"password\": \"password\" }")
.when()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
assertEquals(201, response.getStatusCode()); assertEquals(201, response.getStatusCode());
} }
@Test @Test
public void testMalformedJsonRaisesBadRequestException400() { public void testMalformedJsonRaisesBadRequestException400() {
Response response = given().header("Authorization", "Bearer " + adminToken).contentType(ContentType.JSON) Response response =
.body("{ \"password\"x \"password\" }").when() given()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); .header("Authorization", "Bearer " + adminToken)
.contentType(ContentType.JSON)
.body("{ \"password\"x \"password\" }")
.when()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
assertEquals(400, response.getStatusCode()); assertEquals(400, response.getStatusCode());
} }
@Test @Test
public void testMalformedJsonExceptionMessage() { public void testMalformedJsonExceptionMessage() {
Response response = given().header("Authorization", "Bearer " + adminToken).contentType(ContentType.JSON) Response response =
.body("{ \"password\"x \"password\" }").when() given()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); .header("Authorization", "Bearer " + adminToken)
.contentType(ContentType.JSON)
.body("{ \"password\"x \"password\" }")
.when()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
String error = response.jsonPath().getString("error"); String error = response.jsonPath().getString("error");
...@@ -75,21 +98,28 @@ public class MailPassResourceIntegrationTest { ...@@ -75,21 +98,28 @@ public class MailPassResourceIntegrationTest {
@Test @Test
public void testMissingJsonObjectRaisesBadRequestException400() { public void testMissingJsonObjectRaisesBadRequestException400() {
Response response = given().header("Authorization", "Bearer " + adminToken).contentType(ContentType.JSON) Response response =
.body("{ \"missing_password\": \"password\" }").when() given()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); .header("Authorization", "Bearer " + adminToken)
.contentType(ContentType.JSON)
.body("{ \"missing_password\": \"password\" }")
.when()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
assertEquals(400, response.getStatusCode()); assertEquals(400, response.getStatusCode());
} }
@Test @Test
public void testMissingJsonObjectExceptionMessage() { public void testMissingJsonObjectExceptionMessage() {
Response response = given().header("Authorization", "Bearer " + adminToken).contentType(ContentType.JSON) Response response =
.body("{ \"missing_password\": \"password\" }").when() given()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); .header("Authorization", "Bearer " + adminToken)
.contentType(ContentType.JSON)
.body("{ \"missing_password\": \"password\" }")
.when()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
String error = response.jsonPath().getString("error"); String error = response.jsonPath().getString("error");
assertEquals("password object not detected in provided JSON body", error); assertEquals("password object not detected in provided JSON body", error);
} }
} }
package org.archlinux.keycloak.mailpass.rest; package org.archlinux.keycloak.mailpass.rest;
import static io.restassured.RestAssured.given;
import static org.junit.Assert.assertEquals;
import com.palantir.docker.compose.DockerComposeRule; import com.palantir.docker.compose.DockerComposeRule;
import com.palantir.docker.compose.connection.waiting.HealthChecks; import com.palantir.docker.compose.connection.waiting.HealthChecks;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.response.Response; import io.restassured.response.Response;
import static io.restassured.RestAssured.given;
import static org.junit.Assert.assertEquals;
import java.text.MessageFormat; import java.text.MessageFormat;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
public class MailPassRestResourceIntegrationTest { public class MailPassRestResourceIntegrationTest {
private final static String ROOT_URI = "http://localhost:{0,number,#}/auth/realms/master/mailpass/roleauth"; private static final String ROOT_URI =
private final static String ALT_URI = "http://localhost:{0,number,#}/auth/realms/develop/mailpass/roleauth"; "http://localhost:{0,number,#}/auth/realms/master/mailpass/roleauth";
private final static String TOKEN_GEN_URI = "http://localhost:{0,number,#}/auth/realms/master/protocol/openid-connect/token"; private static final String ALT_URI =
"http://localhost:{0,number,#}/auth/realms/develop/mailpass/roleauth";
private static String testToken; private static final String TOKEN_GEN_URI =
private static int port; "http://localhost:{0,number,#}/auth/realms/master/protocol/openid-connect/token";
@ClassRule private static String testToken;
public static DockerComposeRule docker = DockerComposeRule.builder() private static int port;
.file("src/integrationTest/resources/docker-compose.yml")
.waitingForService("keycloak", HealthChecks.toRespondOverHttp(8080, @ClassRule
(port) -> port.inFormat("http://$HOST:$EXTERNAL_PORT/auth/realms/master"))) public static DockerComposeRule docker =
.build(); DockerComposeRule.builder()
.file("src/integrationTest/resources/docker-compose.yml")
@BeforeClass .waitingForService(
public static void setup() { "keycloak",
port = docker.containers().container("keycloak").port(8080).getExternalPort(); HealthChecks.toRespondOverHttp(
8080, (port) -> port.inFormat("http://$HOST:$EXTERNAL_PORT/auth/realms/master")))
Response response = given().accept(ContentType.JSON).contentType(ContentType.URLENC) .build();
.body("grant_type=password&username=test&password=test&client_id=admin-cli")
.post(MessageFormat.format(TOKEN_GEN_URI, port)); /** Generate bearer token and make it available to all the tests in the class. */
@BeforeClass
testToken = response.jsonPath().getString("access_token"); public static void setup() {
} port = docker.containers().container("keycloak").port(8080).getExternalPort();
@Test Response response =
public void testInvalidRoleReturnsForbidden403() { given()
.accept(ContentType.JSON)
Response response = given().header("Authorization", "Bearer " + testToken).contentType(ContentType.JSON) .contentType(ContentType.URLENC)
.body("{ \"password\": \"password\" }").when() .body("grant_type=password&username=test&password=test&client_id=admin-cli")
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); .post(MessageFormat.format(TOKEN_GEN_URI, port));
assertEquals(403, response.getStatusCode());
} testToken = response.jsonPath().getString("access_token");
}
@Test
public void testInvalidRoleMessage() { @Test
public void testInvalidRoleReturnsForbidden403() {
Response response = given().header("Authorization", "Bearer " + testToken).contentType(ContentType.JSON)
.body("{ \"password\": \"password\" }").when() Response response =
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash"); given()
.header("Authorization", "Bearer " + testToken)
String error = response.jsonPath().getString("error"); .contentType(ContentType.JSON)
.body("{ \"password\": \"password\" }")
assertEquals("Does not have realm admin role", error); .when()
} .post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
assertEquals(403, response.getStatusCode());
@Test }
public void testInvalidRealmReturnsForbidden403() {
@Test
Response response = given().header("Authorization", "Bearer " + testToken).contentType(ContentType.JSON) public void testInvalidRoleMessage() {
.body("{ \"password\": \"password\" }").when()
.post(MessageFormat.format(ALT_URI, port) + "/compute-password-hash"); Response response =
assertEquals(403, response.getStatusCode()); given()
} .header("Authorization", "Bearer " + testToken)
.contentType(ContentType.JSON)
@Test .body("{ \"password\": \"password\" }")
public void testInvalidRealmMessage() { .when()
.post(MessageFormat.format(ROOT_URI, port) + "/compute-password-hash");
Response response = given().header("Authorization", "Bearer " + testToken).contentType(ContentType.JSON)
.body("{ \"password\": \"password\" }").when() String error = response.jsonPath().getString("error");
.post(MessageFormat.format(ALT_URI, port) + "/compute-password-hash");
assertEquals("Does not have realm admin role", error);
String error = response.jsonPath().getString("error"); }
assertEquals("Operation not allowed on this realm", error); @Test
} public void testInvalidRealmReturnsForbidden403() {
Response response =
given()
.header("Authorization", "Bearer " + testToken)
.contentType(ContentType.JSON)
.body("{ \"password\": \"password\" }")
.when()
.post(MessageFormat.format(ALT_URI, port) + "/compute-password-hash");
assertEquals(403, response.getStatusCode());
}
@Test
public void testInvalidRealmMessage() {
Response response =
given()
.header("Authorization", "Bearer " + testToken)
.contentType(ContentType.JSON)
.body("{ \"password\": \"password\" }")
.when()
.post(MessageFormat.format(ALT_URI, port) + "/compute-password-hash");
String error = response.jsonPath().getString("error");
assertEquals("Operation not allowed on this realm", error);
}
} }
package org.archlinux.keycloak.mailpass.rest; package org.archlinux.keycloak.mailpass.rest;
import io.restassured.path.json.JsonPath;
import io.restassured.path.json.exception.JsonPathException;
import java.security.SecureRandom; import java.security.SecureRandom;
import javax.ws.rs.BadRequestException; import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.POST; import javax.ws.rs.POST;
...@@ -9,20 +10,15 @@ import javax.ws.rs.Path; ...@@ -9,20 +10,15 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.bouncycastle.crypto.generators.OpenBSDBCrypt; import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import io.restassured.path.json.JsonPath;
import io.restassured.path.json.exception.JsonPathException;
/** /**
* A custom REST endpoint to trigger functionality on the Keycloak server, which * A custom REST endpoint to trigger functionality on the Keycloak server, which is not available
* is not available through the default set of built-in Keycloak REST endpoints. * through the default set of built-in Keycloak REST endpoints. This is to be used during the
* This is to be used during the storage of a custom attribute on the Account * storage of a custom attribute on the Account Management Console for the mail password. The data
* Management Console for the mail password. The data stored will be a Bcrypt * stored will be a Bcrypt hash instead of the plain text password.
* hash instead of the plain text password.
*/ */
public class MailPassResource { public class MailPassResource {
...@@ -77,5 +73,4 @@ public class MailPassResource { ...@@ -77,5 +73,4 @@ public class MailPassResource {
throw new BadRequestException("provided data is not in valid JSON format"); throw new BadRequestException("provided data is not in valid JSON format");
} }
} }
} }
...@@ -4,9 +4,8 @@ import org.keycloak.models.KeycloakSession; ...@@ -4,9 +4,8 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.resource.RealmResourceProvider;
/** /**
* A provider of a custom REST resource for a path relative to Realm's RESTful * A provider of a custom REST resource for a path relative to Realm's RESTful API that cannot be
* API that cannot be resolved by the Keycloak server. This implementation is a * resolved by the Keycloak server. This implementation is a requirement for custom REST endpoints.
* requirement for custom REST endpoints.
*/ */
public class MailPassResourceProvider implements RealmResourceProvider { public class MailPassResourceProvider implements RealmResourceProvider {
...@@ -14,6 +13,13 @@ public class MailPassResourceProvider implements RealmResourceProvider { ...@@ -14,6 +13,13 @@ public class MailPassResourceProvider implements RealmResourceProvider {
private String realmName; private String realmName;
private String realmRole; private String realmRole;
/**
* Store the pre-configured SPI fields to enable verification before requests take place.
*
* @param session The Keycloak session.
* @param realmName The Keycloak realm under which requests are allowed.
* @param realmRole The role required to submit requests.
*/
public MailPassResourceProvider(KeycloakSession session, String realmName, String realmRole) { public MailPassResourceProvider(KeycloakSession session, String realmName, String realmRole) {
this.session = session; this.session = session;
this.realmName = realmName; this.realmName = realmName;
...@@ -26,7 +32,5 @@ public class MailPassResourceProvider implements RealmResourceProvider { ...@@ -26,7 +32,5 @@ public class MailPassResourceProvider implements RealmResourceProvider {
} }
@Override @Override
public void close() { public void close() {}
}
} }
...@@ -7,9 +7,8 @@ import org.keycloak.services.resource.RealmResourceProvider; ...@@ -7,9 +7,8 @@ import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory; import org.keycloak.services.resource.RealmResourceProviderFactory;
/** /**
* A factory that extends RealmResourceProviderFactory instance to create the * A factory that extends RealmResourceProviderFactory instance to create the custom mail pass
* custom mail pass resource provider. This implementation is a requirement for * resource provider. This implementation is a requirement for custom REST endpoints.
* custom REST endpoints.
*/ */
public class MailPassResourceProviderFactory implements RealmResourceProviderFactory { public class MailPassResourceProviderFactory implements RealmResourceProviderFactory {
...@@ -38,11 +37,8 @@ public class MailPassResourceProviderFactory implements RealmResourceProviderFac ...@@ -38,11 +37,8 @@ public class MailPassResourceProviderFactory implements RealmResourceProviderFac
} }
@Override @Override
public void postInit(KeycloakSessionFactory factory) { public void postInit(KeycloakSessionFactory factory) {}
}
@Override @Override
public void close() { public void close() {}
}
} }
...@@ -8,11 +8,10 @@ import org.keycloak.services.managers.AppAuthManager; ...@@ -8,11 +8,10 @@ import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
/** /**
* A custom REST resource in the form of a domain extension which makes the * A custom REST resource in the form of a domain extension which makes the custom endpoint
* custom endpoint accessible just for authenticated users. The REST request * accessible just for authenticated users. The REST request must be authenticated with bearer
* must be authenticated with bearer access token of an authenticated user and * access token of an authenticated user and the user must be part of a pre-configured realm role in
* the user must be part of a pre-configured realm role in order to access the * order to access the resource.
* resource.
*/ */
public class MailPassRestResource { public class MailPassRestResource {
...@@ -21,6 +20,13 @@ public class MailPassRestResource { ...@@ -21,6 +20,13 @@ public class MailPassRestResource {
private final String realmRole; private final String realmRole;
private final AuthenticationManager.AuthResult auth; private final AuthenticationManager.AuthResult auth;
/**
* Store the pre-configured SPI fields to enable verification before requests take place.
*
* @param session The Keycloak session.
* @param realmName The Keycloak realm under which requests are allowed.
* @param realmRole The role required to submit requests.
*/
public MailPassRestResource(KeycloakSession session, String realmName, String realmRole) { public MailPassRestResource(KeycloakSession session, String realmName, String realmRole) {
this.session = session; this.session = session;
this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate(); this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate();
...@@ -28,6 +34,11 @@ public class MailPassRestResource { ...@@ -28,6 +34,11 @@ public class MailPassRestResource {
this.realmRole = realmRole; this.realmRole = realmRole;
} }
/**
* Ensure the realm and role match the SPI configuration before enabling mailpass requests.
*
* @return MailPassResource instance after successful verification.
*/
@Path("roleauth") @Path("roleauth")
public MailPassResource getMailPassResourceAuthenticated() { public MailPassResource getMailPassResourceAuthenticated() {
checkRealm(); checkRealm();
...@@ -45,9 +56,9 @@ public class MailPassRestResource { ...@@ -45,9 +56,9 @@ public class MailPassRestResource {
private void checkRealmAdmin() { private void checkRealmAdmin() {
if (auth == null) { if (auth == null) {
throw new NotAuthorizedException("Bearer"); throw new NotAuthorizedException("Bearer");
} else if (auth.getToken().getRealmAccess() == null || !auth.getToken().getRealmAccess().isUserInRole(realmRole)) { } else if (auth.getToken().getRealmAccess() == null
|| !auth.getToken().getRealmAccess().isUserInRole(realmRole)) {
throw new ForbiddenException("Does not have realm admin role"); throw new ForbiddenException("Does not have realm admin role");
} }
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment