Skip to content

Commit

Permalink
switch to Quarkus Mailer and create unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
jcschaff committed Jul 9, 2024
1 parent fcff0a0 commit 5734f14
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import cbit.vcell.modeldb.ApiAccessToken;
import cbit.vcell.modeldb.UserIdentity;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.RequestScoped;
Expand All @@ -20,7 +22,6 @@
import org.jose4j.lang.JoseException;
import org.vcell.auth.JWTUtils;
import org.vcell.restq.auth.CustomSecurityIdentityAugmentor;
import org.vcell.restq.db.SMTPService;
import org.vcell.restq.db.UserRestDB;
import org.vcell.util.DataAccessException;
import org.vcell.util.UseridIDExistsException;
Expand All @@ -45,7 +46,7 @@ public class UsersResource {
UriInfo uriInfo;

@Inject
SMTPService smtpService;
Mailer mailer;

private final UserRestDB userRestDB;

Expand Down Expand Up @@ -127,19 +128,14 @@ email, requestorSubject, requestorIssuer, new User(userInfo.userid, userInfo.id)
}
magicLink += "/api/v1/users/processMagicLink" + "?magic="+magicJWT;
String subject = "VCell Account Link Request";
String content = "Dear VCell User,\n" +
"\n" +
"We received a request to link your VCell account for '"+userID+"' associated with this email address. " +
"If you made this request, please click on the link below to confirm your email and link your account:\n" +
"\n" +
"<a href=\""+magicLink+"\">"+magicLink+"<a>\n" +
"\n" +
"If you did not request to link your account, please ignore this email and no changes will be made to your account.\n" +
"\n" +
"Please note that this link will expire in 24 hours, and can only be used once.";

String htmlContent = "<html><body><p>Dear VCell User,</p><p>We received a request to link your VCell account for '"+userID+"' associated with this email address. " +
"If you made this request, please click on the link below to confirm your email and link your account:</p>" +
"<p><a href=\""+magicLink+"\">click here<a></p>" +
"<p>If you did not request to link your account, please ignore this email and no changes will be made to your account.</p>" +
"<p>Please note that this link will expire in 24 hours, and can only be used once.</p></body></html>";
//Send magic link to user
smtpService.sendEmail(userInfo.email, subject, content);
Mail mail = Mail.withHtml(userInfo.email, subject, htmlContent);
mailer.send(mail);
return Response.ok().build();
} catch (Exception e) {
LOG.severe("Error sending magic link email: "+e.getMessage());
Expand Down
6 changes: 3 additions & 3 deletions vcell-rest/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,6 @@ quarkus.swagger-ui.always-include=true

## VCell properties
%dev,test.vcell.softwareVersion=8.0.0
vcell.smtp.hostName=smtp.cam.uchc.edu
vcell.smtp.port=25
vcell.smtp.emailAddress=[email protected]
quarkus.mailer.from="VCell Support <VCell_Support@uchc.edu>"
quarkus.mailer.host=vdsmtp.cam.uchc.edu
quarkus.mailer.port=25
10 changes: 5 additions & 5 deletions vcell-rest/src/main/resources/scripts/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ CREATE INDEX geom_imageref ON vc_geometry(imageRef);
CREATE INDEX mathdesc_geomref ON vc_math(geometryRef);
CREATE INDEX simcstat_simcref ON vc_simcontextstat(simContextRef);

INSERT INTO vc_userinfo VALUES ( 0,'void','1700596370242','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'B9BDD75BC5382CA83D5AB82172A98D869555899C' );
INSERT INTO vc_userinfo VALUES ( 2,'Administrator','1700596370260','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'CD181552B879A2F29D10434D8ACF692B6C8126F9' );
INSERT INTO vc_userinfo VALUES ( 3,'vcellNagios','1700596370261','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'A93453F7962799355608EC89D33D3249474E538F' );
INSERT INTO vc_userinfo VALUES ( 4,'VCellSupport','1714152297847','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'C525EF5192F5DF966D0C891224EC5412FC6FD518' );
INSERT INTO vc_userinfo VALUES ( 140220477,'vcellguest','1700596370260','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'CD181552B879A2F29D10434D8ACF692B6C8126F9' );
INSERT INTO vc_userinfo VALUES ( 0,'void','1700596370242','[email protected]','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'B9BDD75BC5382CA83D5AB82172A98D869555899C' );
INSERT INTO vc_userinfo VALUES ( 2,'Administrator','1700596370260','[email protected]','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'CD181552B879A2F29D10434D8ACF692B6C8126F9' );
INSERT INTO vc_userinfo VALUES ( 3,'vcellNagios','1700596370261','[email protected]','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'A93453F7962799355608EC89D33D3249474E538F' );
INSERT INTO vc_userinfo VALUES ( 4,'VCellSupport','1714152297847','[email protected]','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'C525EF5192F5DF966D0C891224EC5412FC6FD518' );
INSERT INTO vc_userinfo VALUES ( 140220477,'vcellguest','1700596370260','[email protected]','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty','empty',CURRENT_TIMESTAMP,'CD181552B879A2F29D10434D8ACF692B6C8126F9' );
INSERT INTO vc_group VALUES (5,1,0,0,1);
INSERT INTO vc_group VALUES (6,0,0,0,0);
INSERT INTO vc_available VALUES (7,current_timestamp,'true',NULL,NULL);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.vcell.restq.apiclient;

import cbit.vcell.resource.PropertyLoader;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.MockMailbox;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.keycloak.client.KeycloakTestClient;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.lang.JoseException;
import org.junit.jupiter.api.*;
import org.vcell.auth.JWTUtils;
import org.vcell.restclient.ApiClient;
import org.vcell.restclient.ApiException;
import org.vcell.restclient.ApiResponse;
import org.vcell.restclient.api.UsersResourceApi;
import org.vcell.restclient.model.UserIdentityJSONSafe;
import org.vcell.restq.TestEndpointUtils;
import org.vcell.restq.config.CDIVCellConfigProvider;
import org.vcell.restq.db.AgroalConnectionFactory;
import org.vcell.util.DataAccessException;

import java.sql.SQLException;
import java.util.List;

@QuarkusTest
public class RecoverAccountTest {

@Inject
MockMailbox mailbox;

@ConfigProperty(name = "quarkus.http.test-port")
Integer testPort;

@Inject
AgroalConnectionFactory agroalConnectionFactory;

KeycloakTestClient keycloakClient = new KeycloakTestClient();

private ApiClient aliceAPIClient;

@BeforeEach
public void createClients() throws JoseException {
mailbox.clear();

aliceAPIClient = TestEndpointUtils.createAuthenticatedAPIClient(keycloakClient, testPort, TestEndpointUtils.TestOIDCUsers.alice);
RsaJsonWebKey rsaJsonWebKey = JWTUtils.createNewJsonWebKey("k1");
JWTUtils.setRsaJsonWebKey(rsaJsonWebKey);
}
@BeforeAll
public static void setupConfig(){
PropertyLoader.setConfigProvider(new CDIVCellConfigProvider());
}

@AfterEach
public void removeOIDCMappings() throws SQLException, DataAccessException {
TestEndpointUtils.removeAllMappings(agroalConnectionFactory);
}


@Test
public void testRequestRecoveryEmail() throws ApiException {
UsersResourceApi aliceUsersResourceApi = new UsersResourceApi(aliceAPIClient);

// map user
boolean mapped = aliceUsersResourceApi.mapUser(TestEndpointUtils.vcellNagiosUserLoginInfo);
Assertions.assertTrue(mapped);

// unmap user
String userid = TestEndpointUtils.vcellNagiosUserLoginInfo.getUserID();
String email = userid+"@example.com";
Assertions.assertTrue(aliceUsersResourceApi.unmapUser(userid), "Failed to unmap user");

// verify that the user is not mapped
ApiResponse<UserIdentityJSONSafe> mappedUserResponse1 = aliceUsersResourceApi.getMappedUserWithHttpInfo();
Assertions.assertEquals(200, mappedUserResponse1.getStatusCode());
Assertions.assertEquals(Boolean.FALSE, mappedUserResponse1.getData().getMapped());

// request recovery with wrong email
Assertions.assertThrows(ApiException.class, () -> aliceUsersResourceApi.requestRecoveryEmail(email+"abc", userid));
// request recovery with wrong userid
Assertions.assertThrows(ApiException.class, () -> aliceUsersResourceApi.requestRecoveryEmail(email, userid+"abc"));
// request recovery with correct email and userid
ApiResponse<Void> response1 = aliceUsersResourceApi.requestRecoveryEmailWithHttpInfo(email, userid);
Assertions.assertEquals(200, response1.getStatusCode());

// observe the email body captured from the mock SMTP service
List<Mail> capturedEmails = mailbox.getMailsSentTo(email);
Assertions.assertEquals(1, capturedEmails.size());
Mail mail = capturedEmails.get(0);
String body = mail.getHtml();
String magicLink = body.substring(body.indexOf("<a href=\"") + 9, body.indexOf("\">"));
Assertions.assertTrue(magicLink.contains("/api/v1/users/processMagicLink?magic="));
// extract 'magic' query parameter from url in 'magicLink'
String magicLinkToken = magicLink.substring(magicLink.indexOf("magic=") + 6);

ApiResponse<Void> response2 = aliceUsersResourceApi.processMagicLinkWithHttpInfo(magicLinkToken);
Assertions.assertEquals(200, response2.getStatusCode());

// verify that the user is now mapped
ApiResponse<UserIdentityJSONSafe> mappedUserResponse2 = aliceUsersResourceApi.getMappedUserWithHttpInfo();
Assertions.assertEquals(200, mappedUserResponse2.getStatusCode());
Assertions.assertEquals(Boolean.TRUE, mappedUserResponse2.getData().getMapped());
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
package org.vcell.restq.apiclient;

import cbit.vcell.modeldb.UserDbDriver;
import cbit.vcell.resource.PropertyLoader;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import io.quarkus.mailer.MockMailbox;
import io.quarkus.mailer.reactive.ReactiveMailer;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.keycloak.client.KeycloakTestClient;
import jakarta.inject.Inject;
Expand All @@ -29,36 +24,19 @@
import org.vcell.restq.TestEndpointUtils;
import org.vcell.restq.config.CDIVCellConfigProvider;
import org.vcell.restq.db.AgroalConnectionFactory;
import org.vcell.restq.db.UserRestDB;
import org.vcell.restq.handlers.UsersResource;
import org.vcell.util.DataAccessException;
import org.vcell.util.document.User;
import org.vcell.util.document.UserInfo;
import org.vcell.util.document.UserLoginInfo;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

@QuarkusTest
public class UsersApiTest {
@ConfigProperty(name = "quarkus.oidc.auth-server-url")
String authServerUrl;

@ConfigProperty(name = "quarkus.http.test-port")
Integer testPort;

@Inject
MockMailbox mockMailbox;
@Inject
Mailer mailer;

@Inject
AgroalConnectionFactory agroalConnectionFactory;
@Inject
UserRestDB userRestDB;

KeycloakTestClient keycloakClient = new KeycloakTestClient();

private ApiClient aliceAPIClient;
Expand All @@ -85,7 +63,7 @@ public void removeOIDCMappings() throws SQLException, DataAccessException {
public void testMapUser() throws ApiException, InvalidJwtException, MalformedClaimException, JsonProcessingException {
UsersResourceApi aliceUsersResourceApi = new UsersResourceApi(aliceAPIClient);

// map once, true - map twice return false
// map once, true - map twice return true also
boolean mapped = aliceUsersResourceApi.mapUser(TestEndpointUtils.vcellNagiosUserLoginInfo);
Assertions.assertTrue(mapped);
mapped = aliceUsersResourceApi.mapUser(TestEndpointUtils.vcellNagiosUserLoginInfo);
Expand Down Expand Up @@ -132,7 +110,7 @@ public void testMapUser() throws ApiException, InvalidJwtException, MalformedCla
}

@Test
public void testNewUser() throws ApiException, SQLException, DataAccessException, InvalidJwtException, MalformedClaimException {
public void testNewUser() throws ApiException {
UsersResourceApi aliceUsersResourceApi = new UsersResourceApi(aliceAPIClient);

// map once, true - map twice return false
Expand Down Expand Up @@ -221,42 +199,6 @@ public void testOldAPITokenGenerationForMultipleGuests() throws ApiException {
Assertions.assertEquals(guestApiToken.getUserKey(), secondGuestApiToken.getUserKey());
Assertions.assertNotEquals(guestApiToken.getToken(), secondGuestApiToken.getToken());
}

public void testResetPassword() throws SQLException, DataAccessException {
PropertyLoader.setProperty(PropertyLoader.vcellSMTPHostName, "host");
PropertyLoader.setProperty(PropertyLoader.vcellSMTPPort, "9090");
PropertyLoader.setProperty(PropertyLoader.vcellSMTPEmailAddress, "[email protected]");

UserDbDriver userDbDriver = new UserDbDriver();
String startPassword = "1700596370260"; // set in the init.sql script
Object lock = new Object();
Connection connection = agroalConnectionFactory.getConnection(lock);
UserInfo oldUserLoginInfo = userDbDriver.getUserInfo(connection, TestEndpointUtils.administratorUser.getID());

Assertions.assertNotNull(oldUserLoginInfo);
Assertions.assertEquals(new UserLoginInfo.DigestedPassword(startPassword).getString(), oldUserLoginInfo.digestedPassword0.getString());


userDbDriver.sendLostPassword(connection, TestEndpointUtils.administratorUser.getName());
List<Mail> mails = mockMailbox.getMailsSentTo(oldUserLoginInfo.email);
Mail mail = mails.get(0);
String passwordReset = mail.getText();
String[] strings = passwordReset.split("\\.");
strings = strings[0].split(" ");
String newPassword = strings[strings.length - 1];
newPassword = newPassword.replace("'", "");

UserInfo newUserLoginInfo = userDbDriver.getUserInfo(connection, TestEndpointUtils.administratorUser.getID());
Assertions.assertEquals(newUserLoginInfo.digestedPassword0.getString(), new UserLoginInfo.DigestedPassword(newPassword).getString());

// reset changes
userDbDriver.updateUserInfo(connection, oldUserLoginInfo);
newUserLoginInfo = userDbDriver.getUserInfo(connection, TestEndpointUtils.administratorUser.getID());
Assertions.assertEquals(oldUserLoginInfo.digestedPassword0.getString(), newUserLoginInfo.digestedPassword0.getString());

agroalConnectionFactory.release(connection, lock);
}

}


Expand Down

0 comments on commit 5734f14

Please sign in to comment.