Skip to content

Commit

Permalink
MODUSERBL-176 Needs to use new /token/sign endpoint of mod-at (#165)
Browse files Browse the repository at this point in the history
* MODUSERBL-176 Added new endpoint token/sign

* MODUSERBL-176 Added new endpoint token/sign

* MODUSERBL-176 Added log statements

* MODUSERBL-176 Removed log statements

* MODUSERBL-176 Added test cases

* MODUSERBL-176 removed Ok case as its not needed

* Adding to readme and raml for deprecation

* MODUSERBL-176 Removed unused import

---------

Co-authored-by: steveellis <[email protected]>
  • Loading branch information
pavankumar181 and steveellis authored Oct 6, 2023
1 parent 6db6360 commit 6787e22
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 6 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,16 @@ The built artifacts for this module are available.
See [configuration](https://dev.folio.org/download/artifacts) for repository access,
and the [Docker image](https://hub.docker.com/r/folioorg/mod-users-bl/).

### Password reset link expiration

Operators should take care when setting configuration values for `RESET_PASSWORD_LINK_EXPIRATION_TIME` and
`RESET_PASSWORD_LINK_EXPIRATION_UNIT_OF_TIME` to match either the default access token expiration or custom
configured access token configuration for a given tenant, since the token expiration which is tied to the reset will
use this expiration which is set in mod-authtoken. The `RESET_PASSWORD_LINK_EXPIRATION_TIME` and
`RESET_PASSWORD_LINK_EXPIRATION_UNIT_OF_TIME` configuration parameters are used in the notification sent
by the mod-users-bl module.





2 changes: 1 addition & 1 deletion ramls/mod-users-bl.raml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ resourceTypes:
type: { compositeUserResource: { "typeName" : "self reference" } }
/login:
post:
description: Allow a new user to login and return an authtoken, along with a composite user record
description: Allow a new user to login and return an authtoken, along with a composite user record. Deprecated and will be removed in a future release. Please use /login-with-expiry.
is: [permissionsExpandable, includeable]
headers:
User-Agent:
Expand Down
22 changes: 19 additions & 3 deletions src/main/java/org/folio/rest/client/impl/AuthTokenClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.apache.http.HttpStatus;
import org.folio.rest.client.AuthTokenClient;
import org.folio.rest.exception.OkapiModuleClientException;
import org.folio.rest.impl.BLUsersAPI;
import org.folio.rest.util.OkapiConnectionParams;
import org.folio.rest.util.RestUtil;

Expand All @@ -22,14 +21,31 @@ public AuthTokenClientImpl(HttpClient httpClient) {

@Override
public Future<String> signToken(JsonObject tokenPayload, OkapiConnectionParams okapiConnectionParams) {
String requestUrl = okapiConnectionParams.getOkapiUrl() + "/token/sign";
String requestPayload = new JsonObject().put("payload", tokenPayload).encode();

return RestUtil.doRequest(httpClient, requestUrl, HttpMethod.POST, okapiConnectionParams.buildHeaders(), requestPayload)
.compose(response -> {
switch (response.getCode()) {
case HttpStatus.SC_CREATED:
return Future.succeededFuture(response.getJson().getString("token"));
case HttpStatus.SC_NOT_FOUND:
return signTokenLegacy(tokenPayload, okapiConnectionParams);
default:
String logMessage =
String.format("Error when signing token. Status: %d, body: %s", response.getCode(), response.getBody());
throw new OkapiModuleClientException(logMessage);
}
});
}

public Future<String> signTokenLegacy(JsonObject tokenPayload, OkapiConnectionParams okapiConnectionParams) {
String requestUrl = okapiConnectionParams.getOkapiUrl() + "/token";
String requestPayload = new JsonObject().put("payload", tokenPayload).encode();

return RestUtil.doRequest(httpClient, requestUrl, HttpMethod.POST, okapiConnectionParams.buildHeaders(), requestPayload)
.map(response -> {
switch (response.getCode()) {
case HttpStatus.SC_OK:
return response.getResponse().headers().get(BLUsersAPI.OKAPI_TOKEN_HEADER);
case HttpStatus.SC_CREATED:
return response.getJson().getString("token");
default:
Expand Down
154 changes: 152 additions & 2 deletions src/test/java/org/folio/rest/GeneratePasswordRestLinkTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ public void shouldGenerateAndSendPasswordNotificationWhenPasswordWithMinutesOfEx
generateAndSendResetPasswordNotificationWhenPasswordExistsWith(EXPIRATION_TIME_MINUTES,
EXPIRATION_UNIT_OF_TIME_MINUTES, EXPIRATION_TIME_MINUTES, EXPIRATION_UNIT_OF_TIME_MINUTES);
}

@Test
public void shouldGenerateAndSendPasswordNotificationWhenPasswordWithMinutesOfExpirationTimeWhenTokenSignEndPointReturns404AndUsesLegacyToken() {
shouldGenerateAndSendPasswordNotificationWhenPasswordWithMinutesOfExpirationTimeWhenTokenSignEndPointReturns404AndUsesLegacyToken(EXPIRATION_TIME_MINUTES,
EXPIRATION_UNIT_OF_TIME_MINUTES, EXPIRATION_TIME_MINUTES, EXPIRATION_UNIT_OF_TIME_MINUTES);
}
@Test
public void shouldGenerateAndSendPasswordNotificationWhenPasswordWithDaysOfExpirationTime() {
generateAndSendResetPasswordNotificationWhenPasswordExistsWith(EXPIRATION_TIME_DAYS,
Expand Down Expand Up @@ -273,6 +277,120 @@ public void generateAndSendResetPasswordNotificationWhenPasswordExistsWith(
.withRequestBody(WireMock.equalToJson(toJson(expectedNotification), true, true)));
}

public void shouldGenerateAndSendPasswordNotificationWhenPasswordWithMinutesOfExpirationTimeWhenTokenSignEndPointReturns404AndUsesLegacyToken(
String expirationTime, String expirationTimeOfUnit, String expectedExpirationTime,
String expectedExpirationTimeOfUnit) {
Map<String, String> configToMock = new HashMap<>();
configToMock.put(FOLIO_HOST_CONFIG_KEY, MOCK_FOLIO_UI_HOST);
configToMock.put(RESET_PASSWORD_LINK_EXPIRATION_TIME, expirationTime);
configToMock.put(RESET_PASSWORD_LINK_EXPIRATION_UNIT_OF_TIME, expirationTimeOfUnit);
User mockUser = new User()
.withId(UUID.randomUUID().toString())
.withUsername(MOCK_USERNAME);
boolean passwordExists = true;

mockUserFound(mockUser.getId(), mockUser);
mockConfigModule(MODULE_NAME, configToMock);
mockSignAuthTokenLegacy(MOCK_TOKEN);
mockSignAuthTokenNotFound();
mockPostPasswordResetAction(passwordExists);
mockNotificationModule();

JsonObject requestBody = new JsonObject()
.put("userId", mockUser.getId());
String expectedLink = MOCK_FOLIO_UI_HOST + DEFAULT_UI_URL + '/' + MOCK_TOKEN + "?tenant=" + TENANT;
RestAssured.given()
.spec(spec)
.header(mockUrlHeader)
.body(requestBody.encode())
.when()
.post(GENERATE_PASSWORD_RESET_LINK_PATH)
.then()
.statusCode(HttpStatus.SC_OK)
.body("link", is(expectedLink));

List<PasswordResetAction> requestBodyByUrl =
getRequestBodyByUrl(PASSWORD_RESET_ACTION_PATH, PasswordResetAction.class);
assertThat(requestBodyByUrl, hasSize(1));
assertThat(requestBodyByUrl.get(0).getUserId(), is(mockUser.getId()));

Notification expectedNotification = new Notification()
.withEventConfigName(RESET_PASSWORD_EVENT_CONFIG_ID)
.withContext(
new Context()
.withAdditionalProperty("user", mockUser)
.withAdditionalProperty("link", expectedLink)
.withAdditionalProperty("expirationTime", expectedExpirationTime)
.withAdditionalProperty("expirationUnitOfTime", expectedExpirationTimeOfUnit))
.withRecipientId(mockUser.getId());
WireMock.verify(WireMock.postRequestedFor(WireMock.urlMatching(NOTIFY_PATH))
.withRequestBody(WireMock.equalToJson(toJson(expectedNotification), true, true)));
}

public void cannotGeneratePasswordWhenTokenSignEndPointReturns500(
String expirationTime, String expirationTimeOfUnit, String expectedExpirationTime,
String expectedExpirationTimeOfUnit) {
Map<String, String> configToMock = new HashMap<>();
configToMock.put(FOLIO_HOST_CONFIG_KEY, MOCK_FOLIO_UI_HOST);
configToMock.put(RESET_PASSWORD_LINK_EXPIRATION_TIME, expirationTime);
configToMock.put(RESET_PASSWORD_LINK_EXPIRATION_UNIT_OF_TIME, expirationTimeOfUnit);
User mockUser = new User()
.withId(UUID.randomUUID().toString())
.withUsername(MOCK_USERNAME);
boolean passwordExists = true;

mockUserFound(mockUser.getId(), mockUser);
mockConfigModule(MODULE_NAME, configToMock);
mockSignAuthTokenNotFound();
mockSignAuthTokenServerError();
mockPostPasswordResetAction(passwordExists);
mockNotificationModule();

JsonObject requestBody = new JsonObject()
.put("userId", mockUser.getId());
RestAssured.given()
.spec(spec)
.header(mockUrlHeader)
.body(requestBody.encode())
.when()
.post(GENERATE_PASSWORD_RESET_LINK_PATH)
.then()
.statusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);

}

public void cannotGeneratePasswordWhenTokenSignLegacyEndPointReturns500(
String expirationTime, String expirationTimeOfUnit, String expectedExpirationTime,
String expectedExpirationTimeOfUnit) {
Map<String, String> configToMock = new HashMap<>();
configToMock.put(FOLIO_HOST_CONFIG_KEY, MOCK_FOLIO_UI_HOST);
configToMock.put(RESET_PASSWORD_LINK_EXPIRATION_TIME, expirationTime);
configToMock.put(RESET_PASSWORD_LINK_EXPIRATION_UNIT_OF_TIME, expirationTimeOfUnit);
User mockUser = new User()
.withId(UUID.randomUUID().toString())
.withUsername(MOCK_USERNAME);
boolean passwordExists = true;

mockUserFound(mockUser.getId(), mockUser);
mockConfigModule(MODULE_NAME, configToMock);
mockSignAuthTokenNotFound();
mockSignAuthTokenLegacyServerError();
mockPostPasswordResetAction(passwordExists);
mockNotificationModule();

JsonObject requestBody = new JsonObject()
.put("userId", mockUser.getId());
RestAssured.given()
.spec(spec)
.header(mockUrlHeader)
.body(requestBody.encode())
.when()
.post(GENERATE_PASSWORD_RESET_LINK_PATH)
.then()
.statusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);

}

@Test
public void shouldGenerateAndSendCreatePasswordNotificationWhenPasswordExists() {
Map<String, String> configToMock = new HashMap<>();
Expand Down Expand Up @@ -396,6 +514,18 @@ public void shouldGenerateLinkWhenSendNotificationFailed() {
.body("link", is(expectedLink));
}

@Test
public void cannotGeneratePasswordWhenTokenSignEndPointReturns500() {
cannotGeneratePasswordWhenTokenSignEndPointReturns500(EXPIRATION_TIME_MINUTES,
EXPIRATION_UNIT_OF_TIME_MINUTES, EXPIRATION_TIME_MINUTES, EXPIRATION_UNIT_OF_TIME_MINUTES);
}

@Test
public void cannotGeneratePasswordWhenTokenSignLegacyEndPointReturns500() {
cannotGeneratePasswordWhenTokenSignLegacyEndPointReturns500(EXPIRATION_TIME_MINUTES,
EXPIRATION_UNIT_OF_TIME_MINUTES, EXPIRATION_TIME_MINUTES, EXPIRATION_UNIT_OF_TIME_MINUTES);
}

private String toJson(Object object) {
return JsonObject.mapFrom(object).toString();
}
Expand Down Expand Up @@ -427,10 +557,30 @@ private void mockPostPasswordResetAction(boolean passwordExists) {
.willReturn(WireMock.created().withBody(response.encode())));
}

private void mockSignAuthTokenNotFound() {
WireMock.stubFor(WireMock.post("/token/sign")
.willReturn(WireMock.notFound()));
}
private void mockSignAuthToken(String tokenForResponse) {
JsonObject response = new JsonObject().put("token", tokenForResponse);
WireMock.stubFor(WireMock.post("/token/sign")
.willReturn(WireMock.created().withBody(response.encode())));
}

private void mockSignAuthTokenLegacy(String tokenForResponse) {
JsonObject response = new JsonObject().put("token", tokenForResponse);
WireMock.stubFor(WireMock.post("/token")
.willReturn(WireMock.created().withBody(response.encode())));
.willReturn(WireMock.created().withBody(response.encode())));
}

private void mockSignAuthTokenServerError() {
WireMock.stubFor(WireMock.post("/token/sign")
.willReturn(WireMock.serverError()));
}

private void mockSignAuthTokenLegacyServerError() {
WireMock.stubFor(WireMock.post("/token")
.willReturn(WireMock.serverError()));
}

private void mockNotificationModule() {
Expand Down

0 comments on commit 6787e22

Please sign in to comment.