From 290b217456c955adf90f5c6512d6ce6572f36880 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Thu, 13 Feb 2025 16:07:13 -0500 Subject: [PATCH 1/5] Removing legacy routes and tests; fixing indents in tests --- mod-authtoken.iml | 6 + .../auth/authtokenmodule/apis/RouteApi.java | 10 - src/main/resources/openapi/token-1.0.yaml | 52 - .../auth/authtokenmodule/AuthTokenTest.java | 1476 +++++++---------- 4 files changed, 635 insertions(+), 909 deletions(-) create mode 100644 mod-authtoken.iml diff --git a/mod-authtoken.iml b/mod-authtoken.iml new file mode 100644 index 0000000..77c25f0 --- /dev/null +++ b/mod-authtoken.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java b/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java index 8dd34c3..1a24832 100644 --- a/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java +++ b/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java @@ -88,10 +88,6 @@ public RouteApi(Vertx vertx, TokenCreator tokenCreator, UserService userService) // Must come after /invalidate-all because of startsWithMatching in Route.java. addRoute("/token/invalidate", List.of()); addRoute("/_/tenant", List.of()); - // The "legacy" routes. - addRoute("/refreshtoken", List.of("auth.refreshtoken.post")); - // This must be last because of the startsWith matching in Route.java. - addRoute("/token", List.of("auth.token.post")); } private void addRoute(String endpoint, List requiredPermissions) { @@ -103,12 +99,6 @@ public Future createRouter(Vertx vertx) { // Bind the openapi yaml definition with the handler methods defined here. return RouterBuilder.create(vertx, "openapi/token-1.0.yaml") .map(routerBuilder -> { - routerBuilder - .operation("token-legacy") - .handler(this::handleSignLegacyToken); - routerBuilder - .operation("token-sign-legacy") - .handler(this::handleSignRefreshTokenLegacy); routerBuilder .operation("token-refresh") .handler(this::handleRefresh); diff --git a/src/main/resources/openapi/token-1.0.yaml b/src/main/resources/openapi/token-1.0.yaml index 33cdcac..3e6b705 100644 --- a/src/main/resources/openapi/token-1.0.yaml +++ b/src/main/resources/openapi/token-1.0.yaml @@ -6,58 +6,6 @@ paths: # NOTE This module relies on its folio-vertx-lib dependency to handle the /_/tenant # route. Please see its yaml specification for /_/tenant here: # https://dev.folio.org/reference/api/#folio-vertx-lib - /token: - parameters: - # folio-vertx-lib has header files but they don't make the headers required so we override - # that behavior here since these two headers are required. - - $ref: headers/okapi-tenant-required.yaml - - $ref: headers/okapi-url-required.yaml - post: - description: Deprecated. Will be removed in a future release. Please use /token/sign instead. Returns a signed, non-expiring legacy access token. - operationId: token-legacy - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/signTokenPayload" - required: true - responses: - "201": - description: Created and signed token successfully - content: - application/json: - schema: - $ref: "#/components/schemas/tokenResponseLegacy" - "400": - $ref: "#/components/responses/trait_400" - "500": - $ref: "#/components/responses/trait_500" - /refreshtoken: - parameters: - - $ref: headers/okapi-tenant-required.yaml - - $ref: headers/okapi-url-required.yaml - post: - description: | - Returns a signed, expiring refresh token. This is a legacy endpoint and should not be - called by new code and will soon be fully depreciated. - operationId: token-sign-legacy - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/signRefreshToken" - required: true - responses: - "201": - description: Created and signed token successfully - content: - application/json: - schema: - $ref: "#/components/schemas/token" - "400": - $ref: "#/components/responses/trait_400" - "500": - $ref: "#/components/responses/trait_500" /token/sign: parameters: # folio-vertx-lib has header files but they don't make the headers required so we override diff --git a/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java b/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java index fedcdc6..c49debe 100644 --- a/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java +++ b/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java @@ -547,583 +547,384 @@ public void testEmptyTokenWithNoTenant_Legacy() { } @Test - public void testEmptyTokenWithNoUrl_Legacy() { + public void testEmptyTokenWithNoTenant() { + given() + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .post("/token/sign") + .then() + .statusCode(400).body(containsString("Missing header: X-Okapi-Tenant")); + } + + @Test + public void testEmptyTokenWithNoUrl() { given() .header("X-Okapi-Tenant", tenant) .header("X-Okapi-Token", accessToken) .header("Content-type", "application/json") - .post("/token") + .post("/token/sign") .then() .statusCode(400).body(containsString("Missing header: X-Okapi-Url")); } @Test - public void testEmptyTokenSigningRequest_Legacy() { + public void testEmptyTokenSigningRequest() { given() .header("X-Okapi-Tenant", tenant) .header("X-Okapi-Token", accessToken) .header("X-Okapi-Url", "http://localhost:" + freePort) .header("Content-type", "application/json") - .post("/token") + .post("/token/sign") .then() .statusCode(401).body(containsString("Missing required module-level permissions for endpoint")); - } - - @Test - public void testBadTokenSigningRequest_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .body(payloadDummySigningReq.encode()) - .post("/token") - .then() - .statusCode(401) - .body(containsString("Missing required module-level permissions for endpoint '/token': auth.token.post")); - } - - @Test - public void testSigningRequestWithGoodTokenNoPayload_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", moduleTokenLegacy) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .post("/token") - .then() - .statusCode(202); - } - - @Test - public void testSigningRequestGoodDummyTokenGoodPayload_Legacy() throws TokenValidationException { - String token = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token") + "\"]") - .body(new JsonObject().put("payload", payloadDummySigningReq).encode()) - .post("/token") - .then() - .statusCode(201).contentType("application/json").extract().path("token"); - var td = (DummyToken)Token.parse(token, tokenCreator); - assertThat(td.getClaim("sub"), is(payloadDummySigningReq.getString("sub"))); - } - - @Test - public void testSigningRequestGoodAccessTokenGoodPayload_Legacy() throws TokenValidationException { - logger.info("POST signing request with good token, good payload"); - String token = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token") + "\"]") - .body(new JsonObject().put("payload", payloadSigningRequest).encode()) - .post("/token") - .then() - .statusCode(201).contentType("application/json").extract().path("token"); - var lat = (LegacyAccessToken)Token.parse(token, tokenCreator); - assertThat(lat.getClaim("sub"), is(payloadSigningRequest.getString("sub"))); - assertNull(lat.getClaim("exp")); - } - - @Test - public void testSigningRequestUnsupportedMethod_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token") + "\"]") - .body(new JsonObject().put("payload", payloadDummySigningReq).encode()) - .put("/token") - .then() - .statusCode(405); - } - - @Test - public void testSigningRequestGoodTokenBadPayload_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token") + "\"]") - .body("{") - .post("/token") - .then() - .statusCode(400); - } - - @Test - public void testSigningRequestGoodTokenBadPayload2_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token") + "\"]") - .body(new JsonObject().put("noload", payloadDummySigningReq).encode()) - .post("/token") - .then() - .statusCode(400); - } - - @Test - public void testSigningRequestGoodTokenBadPayload3_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token") + "\"]") - .body(new JsonObject().put("payload", new JsonObject().put("x", 1)).encode()) - .post("/token") - .then() - .statusCode(400); - } + } @Test - public void legacyTokenTenants_Legacy() throws ParseException, JOSEException { - var at = new AccessToken("tenant2", "jones", userUUID, - AccessToken.DEFAULT_EXPIRATION_SECONDS).encodeAsJWT(tokenCreator); - + public void testBadTokenSigningRequest() { given() - .header("X-Okapi-Tenant", "tenant2") - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token") + "\"]") - .body(new JsonObject().put("payload", payloadSigningRequest).encode()) - .post("/token") - .then() - .statusCode(404); - - System.clearProperty(LegacyTokenTenants.LEGACY_TOKEN_TENANTS); + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .body(payloadDummySigningReq.encode()) + .post("/token/sign") + .then() + .statusCode(401) + .body(containsString("Missing required module-level permissions for endpoint '/token/sign': auth.token.sign.post")); } - // Methods above this point can be removed when legacy tokens are depreciated. - - @Test - public void testEmptyTokenWithNoTenant() { - given() - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .post("/token/sign") - .then() - .statusCode(400).body(containsString("Missing header: X-Okapi-Tenant")); - } - - @Test - public void testEmptyTokenWithNoUrl() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("Content-type", "application/json") - .post("/token/sign") - .then() - .statusCode(400).body(containsString("Missing header: X-Okapi-Url")); - } - - @Test - public void testEmptyTokenSigningRequest() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .post("/token/sign") - .then() - .statusCode(401).body(containsString("Missing required module-level permissions for endpoint")); - } - - @Test - public void testBadTokenSigningRequest() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .body(payloadDummySigningReq.encode()) - .post("/token/sign") - .then() - .statusCode(401) - .body(containsString("Missing required module-level permissions for endpoint '/token/sign': auth.token.sign.post")); - } - - @Test - public void testSigningRequestWithGoodTokenNoPayload() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", moduleToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .post("/token/sign") - .then() - .statusCode(202); - } - - @Test - public void testSigningRequestGoodDummyTokenGoodPayload() throws TokenValidationException { - String token = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body(new JsonObject().put("payload", payloadDummySigningReq).encode()) - .post("/token/sign") - .then() - .statusCode(201) - .contentType("application/json") - .extract().path("token"); - var td = (DummyTokenExpiring)Token.parse(token, tokenCreator); - assertThat(td.getClaim("sub"), is(payloadDummySigningReq.getString("sub"))); - } - - @Test - public void testSigningRequestGoodAccessTokenGoodPayload() - throws TokenValidationException, JOSEException, ParseException { - logger.info("POST signing request with good token, good payload"); - var response = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body(new JsonObject().put("payload", payloadSigningRequest).encode()) - .post("/token/sign") - .then() - .statusCode(201).contentType("application/json"); - var at =(AccessToken)Token.parse(response.extract().path("accessToken"), tokenCreator); - assertThat(at.getClaim("sub"), is(payloadSigningRequest.getString("sub"))); - assertNotNull(at.getClaim("exp")); - - String encryptedRT = response.extract().path("refreshToken"); - var rt = (RefreshToken)Token.parse(encryptedRT, tokenCreator); - assertThat(rt.getClaim("sub"), is(payloadSigningRequest.getString("sub"))); - assertNotNull(rt.getClaim("exp")); - assertNotNull(rt.getClaim("user_id")); - } - - @Test - public void testRefreshTokenUnsupportedMethod_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/refreshtoken") + "\"]") - .body(new JsonObject().put("userId", userUUID).put("sub", "jones").encode()) - .put("/refreshtoken") - .then() - .statusCode(405); - } - - @Test - public void testRefreshTokenBadPayload_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/refreshtoken") + "\"]") - .body("{") - .post("/refreshtoken") - .then() - .statusCode(400); - } - - @Test - public void testRefreshTokenBadPayload2_Legacy() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/refreshtoken") + "\"]") - .body(new JsonObject().put("sub", "jones").encode()) - .post("/refreshtoken") - .then() - .statusCode(400); - } - - @Test - public void testRefreshToken_Legacy() throws JOSEException, ParseException { - logger.info("POST signing request for a refresh token"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/refreshtoken") + "\"]") - .body(new JsonObject().put("userId", userUUID).put("sub", "jones").encode()) - .post("/refreshtoken") - .then() - .statusCode(201) - .header("Content-Type", "application/json"); - } - - @Test - public void testSigningRequestUnsupportedMethod() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body(new JsonObject().put("payload", payloadDummySigningReq).encode()) - .put("/token/sign") - .then() - .statusCode(405); - } - - @Test - public void testSigningRequestGoodTokenBadPayload() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body("{") - .post("/token/sign") - .then() - .statusCode(400); - } - - @Test - public void testSigningRequestGoodTokenBadPayload2() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body(new JsonObject().put("noload", payloadDummySigningReq).encode()) - .post("/token/sign") - .then() - .statusCode(400); - } - - @Test - public void testSigningRequestGoodTokenBadPayload3() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body(new JsonObject().put("payload", new JsonObject().put("x", 1)).encode()) - .post("/token/sign") - .then() - .statusCode(400); - } - - @Test - public void testRefreshTokenBadPayload() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body("{") - .post("/token/refresh") - .then() - .statusCode(400); - } - - @Test - public void testRefreshTokenBadRefreshToken() { - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", badRefreshToken).encode()) - .post("/token/refresh") - .then() - .statusCode(401).body(containsString("Invalid token")); - } - - @Test - public void testRefreshToken() throws JOSEException, ParseException { - logger.info("POST signing request for a refresh token"); - - var response = getSignTokenResponse(); - String rt = response.extract().path("refreshToken"); - String at = response.extract().path("accessToken"); - - logger.info("PUT /refresh (bad method)"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", rt).encode()) - .put("/token/refresh") - .then() - .statusCode(405); - - String tokenContent = tokenCreator.decodeJWEToken(rt); - - logger.info("POST refresh token with bad tenant"); - String payloadBadTenant = new JsonObject(tokenContent).put("tenant", "foo").encode(); - String refreshTokenBadTenant = tokenCreator.createJWEToken(payloadBadTenant); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", refreshTokenBadTenant).encode()) - .post("/token/refresh") - .then() - .statusCode(403).body(containsString("Invalid token")); - - logger.info("POST refresh token with bad expiry"); - String refreshTokenBadExpiry = tokenCreator.createJWEToken( - new JsonObject(tokenContent).put("exp", 0L).encode()); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", refreshTokenBadExpiry).encode()) - .post("/token/refresh") - .then() - .statusCode(401).body(containsString("Invalid token")); + @Test + public void testSigningRequestWithGoodTokenNoPayload() { + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", moduleToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .post("/token/sign") + .then() + .statusCode(202); + } - logger.info("POST refresh token to get a new refresh and access token"); - final String refreshedAccessToken = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", rt).encode()) - .post("/token/refresh") - .then() - .statusCode(201) - .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.REFRESH_TOKEN)) - .body("$", hasKey(Token.TENANT_ID)) - .extract().body().path(Token.ACCESS_TOKEN); + @Test + public void testSigningRequestGoodDummyTokenGoodPayload() throws TokenValidationException { + String token = given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body(new JsonObject().put("payload", payloadDummySigningReq).encode()) + .post("/token/sign") + .then() + .statusCode(201) + .contentType("application/json") + .extract().path("token"); + var td = (DummyTokenExpiring)Token.parse(token, tokenCreator); + assertThat(td.getClaim("sub"), is(payloadDummySigningReq.getString("sub"))); + } - logger.info(String.format("Test with 'refreshed' token: %s", refreshedAccessToken)); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", refreshedAccessToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-User-Id", userUUID) - .get("/bar") - .then() - .statusCode(202); - } + @Test + public void testSigningRequestGoodAccessTokenGoodPayload() + throws TokenValidationException, JOSEException, ParseException { + logger.info("POST signing request with good token, good payload"); + var response = given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body(new JsonObject().put("payload", payloadSigningRequest).encode()) + .post("/token/sign") + .then() + .statusCode(201).contentType("application/json"); + var at =(AccessToken)Token.parse(response.extract().path("accessToken"), tokenCreator); + assertThat(at.getClaim("sub"), is(payloadSigningRequest.getString("sub"))); + assertNotNull(at.getClaim("exp")); + + String encryptedRT = response.extract().path("refreshToken"); + var rt = (RefreshToken)Token.parse(encryptedRT, tokenCreator); + assertThat(rt.getClaim("sub"), is(payloadSigningRequest.getString("sub"))); + assertNotNull(rt.getClaim("exp")); + assertNotNull(rt.getClaim("user_id")); + } - @Test - public void testRefreshTokenWithBadAddress() throws Exception { - logger.info("POST refresh token with bad address"); - var response = getSignTokenResponse(); - String rt = response.extract().path("refreshToken"); - String at = response.extract().path("accessToken"); - var tokenContent = tokenCreator.decodeJWEToken(rt); - String refreshTokenBadAddress = tokenCreator.createJWEToken( - new JsonObject(tokenContent).put("address", "foo").encode()); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", refreshTokenBadAddress).encode()) - .post("/token/refresh") - .then() - .statusCode(201); - } + @Test + public void testSigningRequestUnsupportedMethod() { + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body(new JsonObject().put("payload", payloadDummySigningReq).encode()) + .put("/token/sign") + .then() + .statusCode(405); + } - @Test - public void testRefreshTokenWhenCrossTenantRequestsDeniedBecauseOfSystemPropertyNotSet() { - logger.info("POST signing request for a refresh token"); + @Test + public void testSigningRequestGoodTokenBadPayload() { + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body("{") + .post("/token/sign") + .then() + .statusCode(400); + } - var response = getSignTokenResponse(); - String rt = response.extract().path("refreshToken"); - String at = response.extract().path("accessToken"); + @Test + public void testSigningRequestGoodTokenBadPayload2() { + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body(new JsonObject().put("noload", payloadDummySigningReq).encode()) + .post("/token/sign") + .then() + .statusCode(400); + } - System.setProperty("allow.cross.tenant.requests", "false"); - logger.info("POST refresh token to get a new refresh and access token with allow.cross.tenant.requests=false"); + @Test + public void testSigningRequestGoodTokenBadPayload3() { + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body(new JsonObject().put("payload", new JsonObject().put("x", 1)).encode()) + .post("/token/sign") + .then() + .statusCode(400); + } - given() - .header("X-Okapi-Tenant", memberTenant) - .header("X-Okapi-Token", at) + @Test + public void testRefreshTokenBadPayload() { + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) .header("X-Okapi-Url", "http://localhost:" + freePort) .header("Content-type", "application/json") .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", rt).encode()) + .body("{") .post("/token/refresh") .then() - .statusCode(403) - .body(containsString("Invalid token"));; - } - - @Test - public void testRefreshTokenWhenCrossTenantRequestsDeniedBecauseOfTenantNotInConsortia() { - logger.info("POST signing request for a refresh token"); - - var response = getSignTokenResponse(); - String rt = response.extract().path("refreshToken"); - String at = response.extract().path("accessToken"); - - System.setProperty("allow.cross.tenant.requests", "true"); - String notConsortiumTenant = "notConsortiumTenant"; - logger.info("POST refresh token to get a new refresh and access token with allow.cross.tenant.requests=true, but not for consortium tenant(/user-tenant returns empty response)"); + .statusCode(400); + } - given() - .header("X-Okapi-Tenant", notConsortiumTenant) - .header("X-Okapi-Token", at) + @Test + public void testRefreshTokenBadRefreshToken() { + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) .header("X-Okapi-Url", "http://localhost:" + freePort) .header("Content-type", "application/json") .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", rt).encode()) + .body(new JsonObject().put("refreshToken", badRefreshToken).encode()) .post("/token/refresh") .then() - .statusCode(403) - .body(containsString("Invalid token")); - } - - @Test - public void testRefreshTokenWhenCrossTenantRequestsAllowed() { - logger.info("POST signing request for a refresh token"); + .statusCode(401).body(containsString("Invalid token")); + } - var response = getSignTokenResponse(); - String rt = response.extract().path("refreshToken"); - String at = response.extract().path("accessToken"); + @Test + public void testRefreshToken() throws JOSEException, ParseException { + logger.info("POST signing request for a refresh token"); - System.setProperty("allow.cross.tenant.requests", "true"); - logger.info("POST refresh token to get a new refresh and access token with allow.cross.tenant.requests=true and member consortium tenant"); + var response = getSignTokenResponse(); + String rt = response.extract().path("refreshToken"); + String at = response.extract().path("accessToken"); - given() - .header("X-Okapi-Tenant", memberTenant) + logger.info("PUT /refresh (bad method)"); + given() + .header("X-Okapi-Tenant", tenant) .header("X-Okapi-Token", at) .header("X-Okapi-Url", "http://localhost:" + freePort) .header("Content-type", "application/json") .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") .body(new JsonObject().put("refreshToken", rt).encode()) - .post("/token/refresh") + .put("/token/refresh") .then() - .statusCode(201); - } + .statusCode(405); + + String tokenContent = tokenCreator.decodeJWEToken(rt); + + logger.info("POST refresh token with bad tenant"); + String payloadBadTenant = new JsonObject(tokenContent).put("tenant", "foo").encode(); + String refreshTokenBadTenant = tokenCreator.createJWEToken(payloadBadTenant); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", refreshTokenBadTenant).encode()) + .post("/token/refresh") + .then() + .statusCode(403).body(containsString("Invalid token")); + + logger.info("POST refresh token with bad expiry"); + String refreshTokenBadExpiry = tokenCreator.createJWEToken( + new JsonObject(tokenContent).put("exp", 0L).encode()); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", refreshTokenBadExpiry).encode()) + .post("/token/refresh") + .then() + .statusCode(401).body(containsString("Invalid token")); + + logger.info("POST refresh token to get a new refresh and access token"); + final String refreshedAccessToken = given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", rt).encode()) + .post("/token/refresh") + .then() + .statusCode(201) + .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.REFRESH_TOKEN)) + .body("$", hasKey(Token.TENANT_ID)) + .extract().body().path(Token.ACCESS_TOKEN); + + logger.info(String.format("Test with 'refreshed' token: %s", refreshedAccessToken)); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", refreshedAccessToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-User-Id", userUUID) + .get("/bar") + .then() + .statusCode(202); + } + + @Test + public void testRefreshTokenWithBadAddress() throws Exception { + logger.info("POST refresh token with bad address"); + var response = getSignTokenResponse(); + String rt = response.extract().path("refreshToken"); + String at = response.extract().path("accessToken"); + var tokenContent = tokenCreator.decodeJWEToken(rt); + String refreshTokenBadAddress = tokenCreator.createJWEToken( + new JsonObject(tokenContent).put("address", "foo").encode()); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", refreshTokenBadAddress).encode()) + .post("/token/refresh") + .then() + .statusCode(201); + } + + @Test + public void testRefreshTokenWhenCrossTenantRequestsDeniedBecauseOfSystemPropertyNotSet() { + logger.info("POST signing request for a refresh token"); + + var response = getSignTokenResponse(); + String rt = response.extract().path("refreshToken"); + String at = response.extract().path("accessToken"); + + System.setProperty("allow.cross.tenant.requests", "false"); + logger.info("POST refresh token to get a new refresh and access token with allow.cross.tenant.requests=false"); + + given() + .header("X-Okapi-Tenant", memberTenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", rt).encode()) + .post("/token/refresh") + .then() + .statusCode(403) + .body(containsString("Invalid token"));; + } + + @Test + public void testRefreshTokenWhenCrossTenantRequestsDeniedBecauseOfTenantNotInConsortia() { + logger.info("POST signing request for a refresh token"); + + var response = getSignTokenResponse(); + String rt = response.extract().path("refreshToken"); + String at = response.extract().path("accessToken"); + + System.setProperty("allow.cross.tenant.requests", "true"); + String notConsortiumTenant = "notConsortiumTenant"; + logger.info("POST refresh token to get a new refresh and access token with allow.cross.tenant.requests=true, but not for consortium tenant(/user-tenant returns empty response)"); + + given() + .header("X-Okapi-Tenant", notConsortiumTenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", rt).encode()) + .post("/token/refresh") + .then() + .statusCode(403) + .body(containsString("Invalid token")); + } + + @Test + public void testRefreshTokenWhenCrossTenantRequestsAllowed() { + logger.info("POST signing request for a refresh token"); + + var response = getSignTokenResponse(); + String rt = response.extract().path("refreshToken"); + String at = response.extract().path("accessToken"); + + System.setProperty("allow.cross.tenant.requests", "true"); + logger.info("POST refresh token to get a new refresh and access token with allow.cross.tenant.requests=true and member consortium tenant"); + + given() + .header("X-Okapi-Tenant", memberTenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", rt).encode()) + .post("/token/refresh") + .then() + .statusCode(201); + } + + private ValidatableResponse getSignTokenResponse() { + return given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body(new JsonObject().put("payload", payloadSigningRequest).encode()) + .post("/token/sign") + .then() + .statusCode(201) + .contentType("application/json") + .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.TENANT_ID)); + } - private ValidatableResponse getSignTokenResponse() { - return given() + @Test + public void testRefreshTokenSingleUse() throws JOSEException, ParseException { + logger.info("POST signing request for a refresh token"); + var response = given() .header("X-Okapi-Tenant", tenant) .header("X-Okapi-Token", accessToken) .header("X-Okapi-Url", "http://localhost:" + freePort) @@ -1134,357 +935,338 @@ private ValidatableResponse getSignTokenResponse() { .then() .statusCode(201) .contentType("application/json") - .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) .body("$", hasKey(Token.TENANT_ID)); - } - - @Test - public void testRefreshTokenSingleUse() throws JOSEException, ParseException { - logger.info("POST signing request for a refresh token"); - var response = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body(new JsonObject().put("payload", payloadSigningRequest).encode()) - .post("/token/sign") - .then() - .statusCode(201) - .contentType("application/json") - .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.TENANT_ID)); - String rt = response.extract().path(Token.REFRESH_TOKEN); - String at = response.extract().path(Token.ACCESS_TOKEN); + String rt = response.extract().path(Token.REFRESH_TOKEN); + String at = response.extract().path(Token.ACCESS_TOKEN); - logger.info("POST refresh token to get a new refresh and access token"); - final String refreshedAccessToken = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", rt).encode()) - .post("/token/refresh") - .then() - .statusCode(201) - .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.REFRESH_TOKEN)) - .body("$", hasKey(Token.TENANT_ID)) - .extract().body().path(Token.ACCESS_TOKEN); + logger.info("POST refresh token to get a new refresh and access token"); + final String refreshedAccessToken = given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", rt).encode()) + .post("/token/refresh") + .then() + .statusCode(201) + .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.REFRESH_TOKEN)) + .body("$", hasKey(Token.TENANT_ID)) + .extract().body().path(Token.ACCESS_TOKEN); - logger.info("POST same refresh token a second time to simulate token attack/leakage"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", refreshedAccessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", rt).encode()) - .post("/token/refresh") - .then() - .statusCode(401).body(is("Invalid token")); - } + logger.info("POST same refresh token a second time to simulate token attack/leakage"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", refreshedAccessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", rt).encode()) + .post("/token/refresh") + .then() + .statusCode(401).body(is("Invalid token")); + } - @Test - public void testAllTokensRevokedAfterOneTokenIsRevoked() throws JOSEException, ParseException { - logger.info("Create three refresh tokens to simulate multiple logins"); - var tokens = createListOfRefreshTokens(); + @Test + public void testAllTokensRevokedAfterOneTokenIsRevoked() throws JOSEException, ParseException { + logger.info("Create three refresh tokens to simulate multiple logins"); + var tokens = createListOfRefreshTokens(); - logger.info("POST one of the refresh tokens to get a new refresh and access token"); - final String newAccessToken = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) // Using global AT for convenience here. - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", tokens.get(1)).encode()) - .post("/token/refresh") - .then() - .statusCode(201) - .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) - .body("$", hasKey(Token.REFRESH_TOKEN)) - .body("$", hasKey(Token.TENANT_ID)) - .extract().body().path(Token.ACCESS_TOKEN); + logger.info("POST one of the refresh tokens to get a new refresh and access token"); + final String newAccessToken = given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) // Using global AT for convenience here. + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", tokens.get(1)).encode()) + .post("/token/refresh") + .then() + .statusCode(201) + .body("$", hasKey(Token.ACCESS_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.REFRESH_TOKEN_EXPIRATION)) + .body("$", hasKey(Token.REFRESH_TOKEN)) + .body("$", hasKey(Token.TENANT_ID)) + .extract().body().path(Token.ACCESS_TOKEN); - logger.info("POST same refresh token a second time to simulate token attack/leakage"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", newAccessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put("refreshToken", tokens.get(1)).encode()) - .post("/token/refresh") - .then() - .statusCode(401).body(is("Invalid token")); + logger.info("POST same refresh token a second time to simulate token attack/leakage"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", newAccessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put("refreshToken", tokens.get(1)).encode()) + .post("/token/refresh") + .then() + .statusCode(401).body(is("Invalid token")); - logger.info("Ensure that all tokens have now been revoked"); - checkAllRefreshTokensAreInvalid(tokens, newAccessToken); - } + logger.info("Ensure that all tokens have now been revoked"); + checkAllRefreshTokensAreInvalid(tokens, newAccessToken); + } - @Test - public void testLogout() throws JOSEException, ParseException { - logger.info("POST signing request for a refresh token for logout"); - var response = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") - .body(new JsonObject().put("payload", payloadSigningRequest).encode()) - .post("/token/sign") - .then() - .statusCode(201); + @Test + public void testLogout() throws JOSEException, ParseException { + logger.info("POST signing request for a refresh token for logout"); + var response = given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/sign") + "\"]") + .body(new JsonObject().put("payload", payloadSigningRequest).encode()) + .post("/token/sign") + .then() + .statusCode(201); - String rt = response.extract().path(Token.REFRESH_TOKEN); - String at = response.extract().path(Token.ACCESS_TOKEN); + String rt = response.extract().path(Token.REFRESH_TOKEN); + String at = response.extract().path(Token.ACCESS_TOKEN); - logger.info("PUT /token/invalidate (bad method)"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate") + "\"]") - .body(new JsonObject().put(Token.REFRESH_TOKEN, rt).encode()) - .put("/token/invalidate") - .then() - .statusCode(405); + logger.info("PUT /token/invalidate (bad method)"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate") + "\"]") + .body(new JsonObject().put(Token.REFRESH_TOKEN, rt).encode()) + .put("/token/invalidate") + .then() + .statusCode(405); - logger.info("POST /token/invalidate (bad request - no RT provided)"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate") + "\"]") - .post("/token/invalidate") - .then() - .statusCode(400); + logger.info("POST /token/invalidate (bad request - no RT provided)"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate") + "\"]") + .post("/token/invalidate") + .then() + .statusCode(400); - logger.info("POST /token/invalidate"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate") + "\"]") - .body(new JsonObject().put(Token.REFRESH_TOKEN, rt).encode()) - .post("/token/invalidate") - .then() - .statusCode(204); + logger.info("POST /token/invalidate"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate") + "\"]") + .body(new JsonObject().put(Token.REFRESH_TOKEN, rt).encode()) + .post("/token/invalidate") + .then() + .statusCode(204); - logger.info("POST /token/refresh (should fail after logout)"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", at) // Using global AT for convenience here. - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") - .body(new JsonObject().put(Token.REFRESH_TOKEN, rt).encode()) - .post("/token/refresh") - .then() - .statusCode(401); - } + logger.info("POST /token/refresh (should fail after logout)"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", at) // Using global AT for convenience here. + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/refresh") + "\"]") + .body(new JsonObject().put(Token.REFRESH_TOKEN, rt).encode()) + .post("/token/refresh") + .then() + .statusCode(401); + } - @Test - public void testLogoutAll() throws JOSEException, ParseException { - logger.info("Create some refresh tokens for logout all test"); - var tokens = createListOfRefreshTokens(); + @Test + public void testLogoutAll() throws JOSEException, ParseException { + logger.info("Create some refresh tokens for logout all test"); + var tokens = createListOfRefreshTokens(); - logger.info("PUT /token/invalidate-all (bad method)"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate-all") + "\"]") - .put("/token/invalidate-all") - .then() - .statusCode(405); + logger.info("PUT /token/invalidate-all (bad method)"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate-all") + "\"]") + .put("/token/invalidate-all") + .then() + .statusCode(405); - logger.info("POST /token/invalidate-all"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("Content-type", "application/json") - .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate-all") + "\"]") - .post("/token/invalidate-all") - .then() - .statusCode(204); + logger.info("POST /token/invalidate-all"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("Content-type", "application/json") + .header("X-Okapi-Permissions", "[\"" + getMagicPermission("/token/invalidate-all") + "\"]") + .post("/token/invalidate-all") + .then() + .statusCode(204); - logger.info("POST /token/refresh (should fail for all tokens created after logout-all)"); - checkAllRefreshTokensAreInvalid(tokens, accessToken); - } + logger.info("POST /token/refresh (should fail for all tokens created after logout-all)"); + checkAllRefreshTokensAreInvalid(tokens, accessToken); + } - @Test - public void testWildCardPermissions() throws JOSEException, ParseException { - logger.info("Test with wildcard 400 /perms/users/id/permissions"); - PermsMock.handlePermsUsersPermissionsStatusCode = 400; - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(400); - PermsMock.handlePermsUsersPermissionsStatusCode = 200; + @Test + public void testWildCardPermissions() throws JOSEException, ParseException { + logger.info("Test with wildcard 400 /perms/users/id/permissions"); + PermsMock.handlePermsUsersPermissionsStatusCode = 400; + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(400); + PermsMock.handlePermsUsersPermissionsStatusCode = 200; - logger.info("Test with wildcard / bad /perms/users/id/permissions"); - PermsMock.handlePermsUsersPermissionsFail = true; - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(400); - PermsMock.handlePermsUsersPermissionsFail = false; + logger.info("Test with wildcard / bad /perms/users/id/permissions"); + PermsMock.handlePermsUsersPermissionsFail = true; + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(400); + PermsMock.handlePermsUsersPermissionsFail = false; - logger.info("Test with wildcard 404 /perms/users/id/permissions"); - PermsMock.handlePermsUsersPermissionsStatusCode = 404; - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(400); - PermsMock.handlePermsUsersPermissionsStatusCode = 200; + logger.info("Test with wildcard 404 /perms/users/id/permissions"); + PermsMock.handlePermsUsersPermissionsStatusCode = 404; + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(400); + PermsMock.handlePermsUsersPermissionsStatusCode = 200; - // Pass a desired permission through as a wildcard. - logger.info("Test with wildcard permission"); - given() - .header("Authtoken-Refresh-Cache", "true") - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(202) - .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); + // Pass a desired permission through as a wildcard. + logger.info("Test with wildcard permission"); + given() + .header("Authtoken-Refresh-Cache", "true") + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(202) + .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); - logger.info("Test with wildcard permission - from cache"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(202); + logger.info("Test with wildcard permission - from cache"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(202); - logger.info("Test with extra /perms/permissions status failure"); - PermsMock.handlePermsPermissionsStatusCode = 400; - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", moduleToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(400); - PermsMock.handlePermsPermissionsStatusCode = 200; + logger.info("Test with extra /perms/permissions status failure"); + PermsMock.handlePermsPermissionsStatusCode = 400; + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", moduleToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(400); + PermsMock.handlePermsPermissionsStatusCode = 200; - logger.info("Test with extra /perms/permissions response error"); - PermsMock.handlePermsPermissionsFail = true; - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", moduleToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(400); - PermsMock.handlePermsPermissionsFail = false; + logger.info("Test with extra /perms/permissions response error"); + PermsMock.handlePermsPermissionsFail = true; + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", moduleToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(400); + PermsMock.handlePermsPermissionsFail = false; - logger.info("Test with extra permissions"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", moduleToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(202) - .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); + logger.info("Test with extra permissions"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", moduleToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(202) + .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); - logger.info("Test with extra permissions cached"); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", moduleToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(202) - .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); + logger.info("Test with extra permissions cached"); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", moduleToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(202) + .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); - logger.info("Test with extra permissions - timeout = 0"); - ModulePermissionsSource.setCacheTimeout(0); - given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", moduleToken) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(202) - .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); - ModulePermissionsSource.setCacheTimeout(60); + logger.info("Test with extra permissions - timeout = 0"); + ModulePermissionsSource.setCacheTimeout(0); + given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", moduleToken) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(202) + .header("X-Okapi-Permissions", "[\"extra.foobar\"]"); + ModulePermissionsSource.setCacheTimeout(60); - logger.info("Test with wildcard permission - zap cache"); - given() - .header("Authtoken-Refresh-Cache", "true") - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", accessToken) - .header("X-Okapi-Url", "http://localhost:" + freePort) - .header("X-Okapi-Permissions-Desired", "extra.*bar") - .get("/bar") - .then() - .statusCode(400); - } + logger.info("Test with wildcard permission - zap cache"); + given() + .header("Authtoken-Refresh-Cache", "true") + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", accessToken) + .header("X-Okapi-Url", "http://localhost:" + freePort) + .header("X-Okapi-Permissions-Desired", "extra.*bar") + .get("/bar") + .then() + .statusCode(400); + } - @Test - public void testExpandSystemPermission() { - Response r = given() - .header("X-Okapi-Tenant", tenant) - .header("X-Okapi-Request-Id", "1234") - .header("X-Okapi-Token", tokenSystemPermission) - .header("X-Okapi-Url", "http://localhost:" + mockPort) - .header("X-Okapi-Permissions-Required", - PermsMock.SYS_PERM_SUB_01 + "," + PermsMock.SYS_PERM_SUB_02) - .get("/testsysperm") - .then() - .statusCode(202) - .extract().response(); + @Test + public void testExpandSystemPermission() { + Response r = given() + .header("X-Okapi-Tenant", tenant) + .header("X-Okapi-Request-Id", "1234") + .header("X-Okapi-Token", tokenSystemPermission) + .header("X-Okapi-Url", "http://localhost:" + mockPort) + .header("X-Okapi-Permissions-Required", + PermsMock.SYS_PERM_SUB_01 + "," + PermsMock.SYS_PERM_SUB_02) + .get("/testsysperm") + .then() + .statusCode(202) + .extract().response(); - String headers = r.getHeader("X-Okapi-Permissions"); - assertTrue(headers.contains(PermsMock.SYS_PERM_SUB_01)); - assertTrue(headers.contains(PermsMock.SYS_PERM_SUB_02)); - } + String headers = r.getHeader("X-Okapi-Permissions"); + assertTrue(headers.contains(PermsMock.SYS_PERM_SUB_01)); + assertTrue(headers.contains(PermsMock.SYS_PERM_SUB_02)); + } @Test public void testAdminHealth() { From 929e27a43b6628068f60a8246fe9144af7354a5d Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Thu, 13 Feb 2025 16:21:56 -0500 Subject: [PATCH 2/5] Removing unwanted file --- mod-authtoken.iml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 mod-authtoken.iml diff --git a/mod-authtoken.iml b/mod-authtoken.iml deleted file mode 100644 index 77c25f0..0000000 --- a/mod-authtoken.iml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file From 3b3b226a9b3a15ac8a2b8aa1bcf54b8a082a94ed Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Thu, 13 Feb 2025 17:07:50 -0500 Subject: [PATCH 3/5] Removing legacy types; removing LegacyTokenTenant; remove from readme --- README.md | 2 - .../auth/authtokenmodule/apis/RouteApi.java | 73 ------------------- .../auth/authtokenmodule/tokens/Token.java | 7 +- .../tokens/legacy/LegacyAccessToken.java | 52 ------------- .../legacy/LegacyTokenTenantException.java | 7 -- .../tokens/legacy/LegacyTokenTenants.java | 47 ------------ .../auth/authtokenmodule/AuthTokenTest.java | 6 -- .../LegacyTokenTenantsTest.java | 54 -------------- .../folio/auth/authtokenmodule/TokenTest.java | 3 +- 9 files changed, 3 insertions(+), 248 deletions(-) delete mode 100644 src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyAccessToken.java delete mode 100644 src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenantException.java delete mode 100644 src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenants.java delete mode 100644 src/test/java/org/folio/auth/authtokenmodule/LegacyTokenTenantsTest.java diff --git a/README.md b/README.md index cd86012..a1b8ca4 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,9 @@ mod-authtoken supports a number of command line options as system properties, se * `cache.permissions` - Boolean controlling the permissions cache. Defaults to `true`. * `allow.cross.tenant.requests` - Boolean to allow (in consortia setups) or deny cross tenant requests. Defaults to `false`. * `token.expiration.seconds` - Override defaults for token expiration in the form of `tenantId:,accessToken:,refreshToken:;accessToken:,refreshToken:`. To override defaults for a specific tenant provide a triplet. To override defaults provide a pair. Separate entries in the string with a `;` character. Neither tenant entries nor a default are required. If a default or a key is not provided, a default of 10 minutes is set by the module for the access token, and a default of one week is set by the module for the refresh token. Note that the invalidate APIs invalidate refresh tokens only. An access token cannot be invalidated and remains valid until its expiration time; this is by design because the access token is stateless. -* `legacy.token.tenants` - A comma separated list of tenant ids for which legacy (non-expiring) tokens are supported. Provide `*` to enable legacy tokens for all tenants or, do not provide the property at all. To disable legacy tokens for all tenants provide the key with an empty value. This will change to the default behavior in a future release. # Environment variables * `TOKEN_EXPIRATION_SECONDS` - Identical to `token.expiration.seconds` as specified above. Provided as a convenience. System property takes precedence. -* `LEGACY_TOKEN_TENANTS` - Identical to `legacy.token.tenanats`. * `DB_HOST` - Postgres hostname. Defaults to `localhost`. * `DB_PORT` - Postgres port. Default to `5432`. * `DB_USERNAME` - Postgres username. Defaults to `root`. diff --git a/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java b/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java index 1a24832..1f47776 100644 --- a/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java +++ b/src/main/java/org/folio/auth/authtokenmodule/apis/RouteApi.java @@ -16,10 +16,6 @@ import org.folio.auth.authtokenmodule.storage.RefreshTokenStore; import org.folio.auth.authtokenmodule.TokenCreator; import org.folio.auth.authtokenmodule.tokens.AccessToken; -import org.folio.auth.authtokenmodule.tokens.DummyToken; -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyTokenTenantException; -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyTokenTenants; -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyAccessToken; import org.folio.auth.authtokenmodule.tokens.DummyTokenExpiring; import org.folio.auth.authtokenmodule.tokens.RefreshToken; import org.folio.auth.authtokenmodule.tokens.Token; @@ -55,7 +51,6 @@ public class RouteApi extends Api implements RouterCreator, TenantInitHooks { private List routes; private Vertx vertx; private TokenExpiration tokenExpiration; - private LegacyTokenTenants legacyTokenTenants; /** * Constructs the API. @@ -72,7 +67,6 @@ public RouteApi(Vertx vertx, TokenCreator tokenCreator, UserService userService) this.tokenCreator = tokenCreator; tokenExpiration = new TokenExpiration(); - legacyTokenTenants = new LegacyTokenTenants(); logger = LogManager.getLogger(RouteApi.class); int permLookupTimeout = Integer.parseInt(System.getProperty("perm.lookup.timeout", "10")); permissionsSource = new ModulePermissionsSource(vertx, permLookupTimeout); @@ -302,71 +296,4 @@ private void handleTokenLogoutAll(RoutingContext ctx) { endText(ctx, 500, "Cannot handle token logout all: " + e.getMessage()); } } - - // Legacy methods. These next two methods can be removed once we stop supporting - // legacy tokens. - - private void handleSignLegacyToken(RoutingContext ctx) { - try { - // X-Okapi-Tenant and X-Okapi-Url are already checked in FilterApi. - String tenant = ctx.request().headers().get(XOkapiHeaders.TENANT); - - // Check for enhanced security mode being enabled for the tenant. If so return 404. - if (!legacyTokenTenants.isLegacyTokenTenant(tenant)) { - var message = "Tenant not a legacy token tenant as specified in this module's environment or system " + - "property. Cannot issue non-expiring legacy token."; - endText(ctx, 404, new LegacyTokenTenantException(message)); - return; - } - - JsonObject json = ctx.body().asJsonObject(); - JsonObject payload; - payload = json.getJsonObject("payload"); - - // Both types of signing requests (dummy and access) have only this property in - // common. - String username = payload.getString("sub"); - Token token; - - // auth 2.0 did not expose the "type" property which is now used internally. But - // other modules like mod-login aren't aware of this type property. Because of this - // dummy token singing requests have a boolean which can be checked to distinguish them from - // regular access token signing requests. - if (isDummyTokenSigningRequest(payload)) { - logger.debug("Signing request is for a dummy token"); - - token = new DummyToken(tenant, payload.getJsonArray("extra_permissions"), username); - } else { - logger.debug("Signing request is for an access token"); - - String userId = payload.getString(USER_ID); - token = new LegacyAccessToken(tenant, username, userId); - - // Clear the user from the permissions cache. - permissionsSource.clearCacheUser(userId, tenant); - } - - logger.debug("Successfully created and signed token"); - - JsonObject responseObject = new JsonObject().put("token", token.encodeAsJWT(tokenCreator)); - endJson(ctx, 201, responseObject.encode()); - } catch (Exception e) { - endText(ctx, 500, e); - } - } - - private void handleSignRefreshTokenLegacy(RoutingContext ctx) { - try { - String tenant = ctx.request().headers().get(XOkapiHeaders.TENANT); - JsonObject requestJson = ctx.body().asJsonObject(); - String userId = requestJson.getString(USER_ID); - String sub = requestJson.getString("sub"); - long expires = tokenExpiration.getRefreshTokenExpiration(tenant); - String refreshToken = new RefreshToken(tenant, sub, userId, expires).encodeAsJWE(tokenCreator); - JsonObject responseJson = new JsonObject().put(Token.REFRESH_TOKEN, refreshToken); - endJson(ctx, 201, responseJson.encode()); - } catch (Exception e) { - endText(ctx, 500, e); - } - } } diff --git a/src/main/java/org/folio/auth/authtokenmodule/tokens/Token.java b/src/main/java/org/folio/auth/authtokenmodule/tokens/Token.java index 50e9b7c..48bd8b2 100644 --- a/src/main/java/org/folio/auth/authtokenmodule/tokens/Token.java +++ b/src/main/java/org/folio/auth/authtokenmodule/tokens/Token.java @@ -10,7 +10,6 @@ import org.apache.logging.log4j.Logger; import org.folio.auth.authtokenmodule.BadSignatureException; import org.folio.auth.authtokenmodule.TokenCreator; -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyAccessToken; import org.folio.okapi.common.XOkapiHeaders; import java.text.ParseException; import java.time.Instant; @@ -221,14 +220,12 @@ public static Token parse(String sourceToken, JsonObject claims) throws TokenVal claims.put("type", DummyToken.TYPE); return new DummyToken(sourceToken, claims); } else { - claims.put("type", LegacyAccessToken.TYPE); - return new LegacyAccessToken(sourceToken, claims); + claims.put("type", AccessToken.TYPE); + return new AccessToken(sourceToken, claims); } } switch (tokenType) { - case LegacyAccessToken.TYPE: - return new LegacyAccessToken(sourceToken, claims); case AccessToken.TYPE: return new AccessToken(sourceToken, claims); case RefreshToken.TYPE: diff --git a/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyAccessToken.java b/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyAccessToken.java deleted file mode 100644 index 3161b14..0000000 --- a/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyAccessToken.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.folio.auth.authtokenmodule.tokens.legacy; - -import java.time.Instant; -import io.vertx.core.Future; -import io.vertx.core.json.JsonObject; -import org.folio.auth.authtokenmodule.tokens.RefreshToken; -import org.folio.auth.authtokenmodule.tokens.Token; -import org.folio.auth.authtokenmodule.tokens.TokenValidationContext; - -/** - * Access tokens are obtained either when a user authenticates or when a valid - * refresh token is provided. The legacy access token is a non-expiring access token - * which should eventually be depreciated. - * @see RefreshToken - */ -public class LegacyAccessToken extends Token { - /** - * A string representation of the type of this token. - */ - public static final String TYPE = "legacy-access"; - - /** - * Create a new access token. - * @param tenant The current tenant. - * @param username The username associated with the token. - * @param userId The user id associated with the token. - */ - public LegacyAccessToken(String tenant, String username, String userId) { - var now = Instant.now().getEpochSecond(); - claims = new JsonObject(); - claims.put("type", TYPE); - claims.put("iat", now); - claims.put("tenant", tenant); - claims.put("sub", username); - claims.put("user_id", userId); - } - - /** - * Instantiate an access token object from a token which has been provided - * for authorization. - * @param jwtSource The token that has been provided. - * @param sourceClaims The claims for the source token. - */ - public LegacyAccessToken(String jwtSource, JsonObject sourceClaims) { - claims = sourceClaims; - source = jwtSource; - } - - protected Future validateContext(TokenValidationContext context) { - return validateCommon(context); - } -} diff --git a/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenantException.java b/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenantException.java deleted file mode 100644 index f41df9e..0000000 --- a/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenantException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.folio.auth.authtokenmodule.tokens.legacy; - -public class LegacyTokenTenantException extends RuntimeException { - public LegacyTokenTenantException(String message) { - super(message); - } -} diff --git a/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenants.java b/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenants.java deleted file mode 100644 index d81c103..0000000 --- a/src/main/java/org/folio/auth/authtokenmodule/tokens/legacy/LegacyTokenTenants.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.folio.auth.authtokenmodule.tokens.legacy; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class LegacyTokenTenants { - public static final String LEGACY_TOKEN_TENANTS = "legacy.token.tenants"; - public static final String LEGACY_TOKEN_TENANTS_ENV = "LEGACY_TOKEN_TENANTS"; - public static final String ALL_TENANTS_LEGACY_CONFIG = "*"; - - private final List tenants; - private boolean allTenantsLegacy; - - public boolean isLegacyTokenTenant(String tenantId) { - if (this.allTenantsLegacy) { - return true; - } - return tenants.contains(tenantId); - } - - public LegacyTokenTenants() { - this.tenants = parseTenants(); - } - - private List parseTenants() { - var prop = getTenantsFromEnvOrSystemProperty(); - if (prop == null || ALL_TENANTS_LEGACY_CONFIG.equals(prop.trim())) { - this.allTenantsLegacy = true; - return Collections.emptyList(); - } - - if (prop.trim().isEmpty()) { - return Collections.emptyList(); - } - - return Arrays.asList(prop.replace(" ", "").split(",")); - } - - private String getTenantsFromEnvOrSystemProperty() { - var prop = System.getProperty(LEGACY_TOKEN_TENANTS); - if (prop != null) { - return prop; - } - return System.getenv(LEGACY_TOKEN_TENANTS_ENV); - } -} diff --git a/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java b/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java index c49debe..a7a0d31 100644 --- a/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java +++ b/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java @@ -31,8 +31,6 @@ import org.folio.auth.authtokenmodule.tokens.RefreshToken; import org.folio.auth.authtokenmodule.tokens.Token; import org.folio.auth.authtokenmodule.tokens.TokenValidationException; -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyTokenTenants; -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyAccessToken; import org.folio.okapi.common.XOkapiHeaders; import org.folio.tlib.postgres.testing.TenantPgPoolContainer; import org.junit.runner.RunWith; @@ -44,7 +42,6 @@ import static io.restassured.RestAssured.*; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; @@ -82,15 +79,12 @@ public class AuthTokenTest { static int mockPort; static int freePort; static Vertx vertx; - Async async; @ClassRule public static PostgreSQLContainer postgresSQLContainer = TenantPgPoolContainer.create(); @BeforeClass public static void setUpClass(TestContext context) throws JOSEException, ParseException { - System.setProperty(LegacyTokenTenants.LEGACY_TOKEN_TENANTS, tenant); - port = NetworkUtils.nextFreePort(); mockPort = NetworkUtils.nextFreePort(); freePort = NetworkUtils.nextFreePort(); diff --git a/src/test/java/org/folio/auth/authtokenmodule/LegacyTokenTenantsTest.java b/src/test/java/org/folio/auth/authtokenmodule/LegacyTokenTenantsTest.java deleted file mode 100644 index ada7d1d..0000000 --- a/src/test/java/org/folio/auth/authtokenmodule/LegacyTokenTenantsTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.folio.auth.authtokenmodule; - -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyTokenTenants; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -class LegacyTokenTenantsTest { - - @BeforeEach - @AfterEach - public void clearSystemProperty() { - System.clearProperty(LegacyTokenTenants.LEGACY_TOKEN_TENANTS); - } - - @ParameterizedTest - @ValueSource(strings = { "tenant1", "tenant2, tenant1", "tenant2,tenant1" }) // Only configured tenants are allowed. - void legacyTokenTenantsConfigPositive(String tenants) { - System.setProperty(LegacyTokenTenants.LEGACY_TOKEN_TENANTS, tenants); - - var legacyTenants = new LegacyTokenTenants(); - assertThat(legacyTenants.isLegacyTokenTenant("tenant1"), is(true)); - assertThat(legacyTenants.isLegacyTokenTenant("tenant3"), is(false)); - } - - @ParameterizedTest - @ValueSource(strings = { "*", " * " }) // All tenants are allowed. - void legacyTokenTenantsConfigAllTenants(String tenants) { - System.setProperty(LegacyTokenTenants.LEGACY_TOKEN_TENANTS, tenants); - - var legacyTenants = new LegacyTokenTenants(); - assertThat(legacyTenants.isLegacyTokenTenant("tenant1"), is(true)); - } - - @ParameterizedTest - @ValueSource(strings = { "", " " }) // No tenants are allowed. - void legacyTokenTenantsConfigNoTenants(String tenants) { - System.setProperty(LegacyTokenTenants.LEGACY_TOKEN_TENANTS, tenants); - - var legacyTenants = new LegacyTokenTenants(); - assertThat(legacyTenants.isLegacyTokenTenant("tenant1"), is(false)); - } - - @Test - void legacyTokenTenantsConfigNoConfig() { // No config, so all are allowed. - var legacyTenants = new LegacyTokenTenants(); - assertThat(legacyTenants.isLegacyTokenTenant("tenant1"), is(true)); - } -} diff --git a/src/test/java/org/folio/auth/authtokenmodule/TokenTest.java b/src/test/java/org/folio/auth/authtokenmodule/TokenTest.java index 81e1fd7..92186a3 100644 --- a/src/test/java/org/folio/auth/authtokenmodule/TokenTest.java +++ b/src/test/java/org/folio/auth/authtokenmodule/TokenTest.java @@ -12,7 +12,6 @@ import io.vertx.core.net.SocketAddress; import org.folio.auth.authtokenmodule.tokens.AccessToken; import org.folio.auth.authtokenmodule.tokens.DummyToken; -import org.folio.auth.authtokenmodule.tokens.legacy.LegacyAccessToken; import org.folio.auth.authtokenmodule.tokens.RefreshToken; import org.folio.auth.authtokenmodule.tokens.DummyTokenExpiring; import org.folio.auth.authtokenmodule.tokens.Token; @@ -130,7 +129,7 @@ public void noTypeLegacyAccess() throws TokenValidationException { JsonObject claims = new JsonObject() .put("tenant", "lib"); Token parse = Token.parse(".", claims); - assertThat(parse, is(instanceOf(LegacyAccessToken.class))); + assertThat(parse, is(instanceOf(AccessToken.class))); } @Test From 477a667b68da7c5e738404360e4059bb61326722 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Fri, 14 Feb 2025 14:37:30 -0500 Subject: [PATCH 4/5] Removing unused variable --- .../java/org/folio/auth/authtokenmodule/AuthTokenTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java b/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java index a7a0d31..9fbf9ba 100644 --- a/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java +++ b/src/test/java/org/folio/auth/authtokenmodule/AuthTokenTest.java @@ -61,7 +61,6 @@ public class AuthTokenTest { private static TokenCreator tokenCreator; private static String userUUID = "007d31d2-1441-4291-9bb8-d6e2c20e399a"; private static String accessToken; - private static String moduleTokenLegacy; private static String moduleToken; private static String dummyToken; @@ -102,8 +101,6 @@ public static void setUpClass(TestContext context) throws JOSEException, ParseEx tokenCreator = new TokenCreator(passPhrase); accessToken = new AccessToken(tenant, "jones", userUUID, AccessToken.DEFAULT_EXPIRATION_SECONDS).encodeAsJWT(tokenCreator); - moduleTokenLegacy = new ModuleToken(tenant, "jones", userUUID, "", new JsonArray().add("auth.token.post")) - .encodeAsJWT(tokenCreator); moduleToken = new ModuleToken(tenant, "jones", userUUID, "", new JsonArray().add("auth.token.sign.post")) .encodeAsJWT(tokenCreator); var extraPerms = new JsonArray().add("auth.token.post").add(PermsMock.SYS_PERM_SET).add("abc.def"); From eb8e25c41cd65d0fafbf089c3899a24601104979 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Mon, 17 Feb 2025 12:05:22 -0500 Subject: [PATCH 5/5] Forgot to remove from MD --- descriptors/ModuleDescriptor-template.json | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index bc970ee..ec28c24 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -2,22 +2,6 @@ "id": "${artifactId}-${version}", "name": "authtoken", "provides": [ - { - "id": "authtoken", - "version": "2.1", - "handlers": [ - { - "methods": [ "POST" ], - "pathPattern": "/token", - "permissionsRequired": [ "auth.token.post" ] - }, - { - "methods": [ "POST" ], - "pathPattern": "/refreshtoken", - "permissionsRequired": [ "auth.refreshtoken.post" ] - } - ] - }, { "id": "authtoken2", "version": "1.1",