Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#11878] Add status and comments to AccountRequest #12898

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.googlecode.objectify.cmd.Query;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.storage.sqlentity.AccountRequest;

/**
Expand Down Expand Up @@ -42,7 +43,9 @@ protected void migrateEntity(teammates.storage.entity.AccountRequest oldEntity)
AccountRequest newEntity = new AccountRequest(
oldEntity.getEmail(),
oldEntity.getName(),
oldEntity.getInstitute());
oldEntity.getInstitute(),
AccountRequestStatus.APPROVED,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we set all the old account requests to approved, is it possible to result in inconsistencies with Accounts db?
Eg. if some account request in datastore has registeredAt == null, so not approved yet. If we migrate, then that account request will have status = approve, but regKey and regAt are null and no account in accounts db.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding, the feature branch will only be merged after the migration is completed, right? Is this no longer the case? Assuming this is still the case, the script should not be used anymore when this is merged so I was not too careful on this. This was mainly placed there to prevent compilation errors. 😅

Regardless, I did specifically choose APPROVED because it does in fact correspond to registeredAt being null. What you are describing, where registeredAt is non-null and an account has been created, actually corresponds to REGISTERED.

There was also a discussion on this in the issue. It was long ago though, so maybe you missed it. Just in case, see:

Of course, there is the case where it is set to APPROVED when it should actually in fact be REGISTERED, but I felt it was not a big problem because of the assumption above, and because the account was already created so the account request could then already be ignored.

Still, I can try to change this so that it is more accurate by making it such that it is APPROVED if registeredAt is null or REGISTERED otherwise, instead of being default APPROVED always. This way, it should be correct even if migration is done after the feature branch is merged. In order for the other PR that depends on this to be submitted, let's merge this first; I will make this change to the migration script in a separate PR. I hope this is okay, @EuniceSim142

null);

// set registration key to the old value if exists
if (oldEntity.getRegistrationKey() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import teammates.client.connector.DatastoreClient;
import teammates.client.util.ClientProperties;
import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.util.HibernateUtil;
import teammates.storage.entity.UsageStatistics;
import teammates.storage.sqlentity.Notification;
Expand Down Expand Up @@ -43,7 +44,9 @@ protected void verifySqlConnection() {
teammates.storage.sqlentity.AccountRequest newEntity = new teammates.storage.sqlentity.AccountRequest(
"dummy-teammates-account-request-email@gmail.com",
"dummy-teammates-account-request",
"dummy-teammates-institute");
"dummy-teammates-institute",
AccountRequestStatus.PENDING,
"dummy-comments");
HibernateUtil.beginTransaction();
HibernateUtil.persist(newEntity);
HibernateUtil.commitTransaction();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.testng.annotations.Test;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.exception.EntityAlreadyExistsException;
import teammates.common.exception.EntityDoesNotExistException;
import teammates.common.exception.InvalidParametersException;
Expand All @@ -28,8 +29,10 @@ public void testResetAccountRequest()
String name = "name lee";
String email = "email@gmail.com";
String institute = "institute";
AccountRequestStatus status = AccountRequestStatus.PENDING;
String comments = "comments";

AccountRequest toReset = accountRequestsLogic.createAccountRequest(name, email, institute);
AccountRequest toReset = accountRequestsLogic.createAccountRequest(name, email, institute, status, comments);
AccountRequestsDb accountRequestsDb = AccountRequestsDb.inst();

toReset.setRegisteredAt(Instant.now());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.datatransfer.FeedbackParticipantType;
import teammates.common.datatransfer.InstructorPermissionRole;
import teammates.common.datatransfer.InstructorPrivileges;
Expand Down Expand Up @@ -62,7 +63,7 @@ public void testCreateDataBundle_typicalValues_createdCorrectly() throws Excepti

AccountRequest actualAccountRequest = dataBundle.accountRequests.get("instructor1");
AccountRequest expectedAccountRequest = new AccountRequest("instr1@teammates.tmt", "Instructor 1",
"TEAMMATES Test Institute 1");
"TEAMMATES Test Institute 1", AccountRequestStatus.REGISTERED, "These are some comments.");
expectedAccountRequest.setId(actualAccountRequest.getId());
expectedAccountRequest.setRegisteredAt(Instant.parse("2015-02-14T00:00:00Z"));
expectedAccountRequest.setRegistrationKey(actualAccountRequest.getRegistrationKey());
Expand Down
36 changes: 24 additions & 12 deletions src/it/java/teammates/it/storage/sqlapi/AccountRequestsDbIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.testng.annotations.Test;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.exception.EntityDoesNotExistException;
import teammates.it.test.BaseTestCaseWithSqlDatabaseAccess;
import teammates.storage.sqlapi.AccountRequestsDb;
Expand All @@ -20,7 +21,8 @@ public class AccountRequestsDbIT extends BaseTestCaseWithSqlDatabaseAccess {
public void testCreateReadDeleteAccountRequest() throws Exception {
______TS("Create account request, does not exists, succeeds");

AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");
accountRequestDb.createAccountRequest(accountRequest);

______TS("Read account request using the given email and institute");
Expand Down Expand Up @@ -53,7 +55,7 @@ public void testCreateReadDeleteAccountRequest() throws Exception {
______TS("Create account request, same email address and institute already exist, creates successfully");

AccountRequest identicalAccountRequest =
new AccountRequest("test@gmail.com", "name", "institute");
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");
assertNotSame(accountRequest, identicalAccountRequest);

accountRequestDb.createAccountRequest(identicalAccountRequest);
Expand All @@ -74,7 +76,8 @@ public void testCreateReadDeleteAccountRequest() throws Exception {
public void testUpdateAccountRequest() throws Exception {
______TS("Update account request, does not exists, exception thrown");

AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");

assertThrows(EntityDoesNotExistException.class,
() -> accountRequestDb.updateAccountRequest(accountRequest));
Expand All @@ -96,7 +99,8 @@ public void testSqlInjectionInCreateAccountRequestEmailField() throws Exception

// Attempt to use SQL commands in email field
String email = "email'/**/OR/**/1=1/**/@gmail.com";
AccountRequest accountRequest = new AccountRequest(email, "name", "institute");
AccountRequest accountRequest =
new AccountRequest(email, "name", "institute", AccountRequestStatus.PENDING, "comments");

// The system should treat the input as a plain text string
accountRequestDb.createAccountRequest(accountRequest);
Expand All @@ -110,7 +114,8 @@ public void testSqlInjectionInCreateAccountRequestNameField() throws Exception {

// Attempt to use SQL commands in name field
String name = "name'; SELECT * FROM account_requests; --";
AccountRequest accountRequest = new AccountRequest("test@gmail.com", name, "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", name, "institute", AccountRequestStatus.PENDING, "comments");

// The system should treat the input as a plain text string
accountRequestDb.createAccountRequest(accountRequest);
Expand All @@ -124,7 +129,8 @@ public void testSqlInjectionInCreateAccountRequestInstituteField() throws Except

// Attempt to use SQL commands in institute field
String institute = "institute'; DROP TABLE account_requests; --";
AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", institute);
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", institute, AccountRequestStatus.PENDING, "comments");

// The system should treat the input as a plain text string
accountRequestDb.createAccountRequest(accountRequest);
Expand All @@ -136,7 +142,8 @@ public void testSqlInjectionInCreateAccountRequestInstituteField() throws Except
public void testSqlInjectionInGetAccountRequest() throws Exception {
______TS("SQL Injection test in getAccountRequest");

AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");
accountRequestDb.createAccountRequest(accountRequest);

String instituteInjection = "institute'; DROP TABLE account_requests; --";
Expand All @@ -151,7 +158,8 @@ public void testSqlInjectionInGetAccountRequest() throws Exception {
public void testSqlInjectionInGetAccountRequestByRegistrationKey() throws Exception {
______TS("SQL Injection test in getAccountRequestByRegistrationKey");

AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");
accountRequestDb.createAccountRequest(accountRequest);

String regKeyInjection = "regKey'; DROP TABLE account_requests; --";
Expand All @@ -166,7 +174,8 @@ public void testSqlInjectionInGetAccountRequestByRegistrationKey() throws Except
public void testSqlInjectionInUpdateAccountRequest() throws Exception {
______TS("SQL Injection test in updateAccountRequest");

AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");
accountRequestDb.createAccountRequest(accountRequest);

String nameInjection = "newName'; DROP TABLE account_requests; --";
Expand All @@ -181,13 +190,15 @@ public void testSqlInjectionInUpdateAccountRequest() throws Exception {
public void testSqlInjectionInDeleteAccountRequest() throws Exception {
______TS("SQL Injection test in deleteAccountRequest");

AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");
accountRequestDb.createAccountRequest(accountRequest);

String emailInjection = "email'/**/OR/**/1=1/**/@gmail.com";
String nameInjection = "name'; DROP TABLE account_requests; --";
String instituteInjection = "institute'; DROP TABLE account_requests; --";
AccountRequest accountRequestInjection = new AccountRequest(emailInjection, nameInjection, instituteInjection);
AccountRequest accountRequestInjection = new AccountRequest(emailInjection, nameInjection, instituteInjection,
AccountRequestStatus.PENDING, "comments");
accountRequestDb.deleteAccountRequest(accountRequestInjection);

AccountRequest actual = accountRequestDb.getAccountRequest(accountRequest.getEmail(), accountRequest.getInstitute());
Expand All @@ -198,7 +209,8 @@ public void testSqlInjectionInDeleteAccountRequest() throws Exception {
public void testSqlInjectionSearchAccountRequestsInWholeSystem() throws Exception {
______TS("SQL Injection test in searchAccountRequestsInWholeSystem");

AccountRequest accountRequest = new AccountRequest("test@gmail.com", "name", "institute");
AccountRequest accountRequest =
new AccountRequest("test@gmail.com", "name", "institute", AccountRequestStatus.PENDING, "comments");
accountRequestDb.createAccountRequest(accountRequest);

String searchInjection = "institute'; DROP TABLE account_requests; --";
Expand Down
2 changes: 2 additions & 0 deletions src/it/resources/data/DataBundleLogicIT.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"name": "Instructor 1",
"email": "instr1@teammates.tmt",
"institute": "TEAMMATES Test Institute 1",
"status": "REGISTERED",
"comments": "These are some comments.",
"registeredAt": "2015-02-14T00:00:00Z"
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package teammates.common.datatransfer;

/**
* The status of an account request.
*/
public enum AccountRequestStatus {

/**
* The account request has not yet been processed by the admin.
*/
PENDING,

/**
* The account request has been rejected by the admin.
*/
REJECTED,

/**
* The account request has been approved by the admin but the instructor has not created an account yet.
*/
APPROVED,

/**
* The account request has been approved by the admin and the instructor has created an account.
*/
REGISTERED
}
7 changes: 4 additions & 3 deletions src/main/java/teammates/sqllogic/api/Logic.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import javax.annotation.Nullable;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.datatransfer.FeedbackQuestionRecipient;
import teammates.common.datatransfer.FeedbackResultFetchType;
import teammates.common.datatransfer.NotificationStyle;
Expand Down Expand Up @@ -88,10 +89,10 @@ public static Logic inst() {
* @throws InvalidParametersException if the account request details are invalid.
* @throws EntityAlreadyExistsException if the account request already exists.
*/
public AccountRequest createAccountRequest(String name, String email, String institute)
throws InvalidParametersException {
public AccountRequest createAccountRequest(String name, String email, String institute, AccountRequestStatus status,
String comments) throws InvalidParametersException {

return accountRequestLogic.createAccountRequest(name, email, institute);
return accountRequestLogic.createAccountRequest(name, email, institute, status, comments);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.exception.EntityDoesNotExistException;
import teammates.common.exception.InvalidParametersException;
import teammates.common.exception.SearchServiceException;
Expand Down Expand Up @@ -57,9 +58,9 @@ public AccountRequest createAccountRequest(AccountRequest accountRequest) throws
/**
* Creates an account request.
*/
public AccountRequest createAccountRequest(String name, String email, String institute)
throws InvalidParametersException {
AccountRequest toCreate = new AccountRequest(email, name, institute);
public AccountRequest createAccountRequest(String name, String email, String institute, AccountRequestStatus status,
String comments) throws InvalidParametersException {
AccountRequest toCreate = new AccountRequest(email, name, institute, status, comments);

return accountRequestDb.createAccountRequest(toCreate);
}
Expand Down
34 changes: 31 additions & 3 deletions src/main/java/teammates/storage/sqlentity/AccountRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@

import org.hibernate.annotations.UpdateTimestamp;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.util.Config;
import teammates.common.util.Const;
import teammates.common.util.FieldValidator;
import teammates.common.util.SanitizationHelper;
import teammates.common.util.StringHelper;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
Expand All @@ -40,6 +44,12 @@ public class AccountRequest extends BaseEntity {

private String institute;

@Enumerated(EnumType.STRING)
private AccountRequestStatus status;

@Column(columnDefinition = "TEXT")
private String comments;
Copy link
Contributor

@xenosf xenosf Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the plural name comments be somewhat ambiguous? A singular form name might be clearer in indicating that it is a single String and not a collection. (same with the other String comments elsewhere in the code)

Copy link
Contributor Author

@jayasting98 jayasting98 Mar 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also thinking this. However, I felt the singular, comment, didn't feel exactly right either. Also, I saw teammates.storage.sqlentity.Student also had a similar plural comments attribute, despite not being a collection, but a mere string.

/**
* Represents a Student.
*/
@Entity
@Table(name = "Students")
public class Student extends User {
@Column(nullable = false)
private String comments;

Nevertheless, I can change this, but I think we will also have to change this in the tech design doc (I see it in some places there too), if you still insist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it is hard to find an alternative name that sounds right. (I was thinking something along the lines of commentsText, but that seems a bit clunky.)

There may be a bit of confusion as the words "comment"/"comments" is also used in the context of feedback response comments. In that context, the plural "comments" would almost always be referring to a collection.

However, seeing as that context is quite separate from this one, leaving the string's name as comments is probably OK too.

Copy link
Contributor Author

@jayasting98 jayasting98 Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xenosf I think for this one, we can consider merging for now to get the ball rolling first. We can rename it later, if necessary, before the ARF feature branch is merged.


private Instant registeredAt;

@UpdateTimestamp
Expand All @@ -49,11 +59,13 @@ protected AccountRequest() {
// required by Hibernate
}

public AccountRequest(String email, String name, String institute) {
public AccountRequest(String email, String name, String institute, AccountRequestStatus status, String comments) {
this.setId(UUID.randomUUID());
this.setEmail(email);
this.setName(name);
this.setInstitute(institute);
this.setStatus(status);
this.setComments(comments);
this.generateNewRegistrationKey();
this.setCreatedAt(Instant.now());
this.setRegisteredAt(null);
Expand Down Expand Up @@ -128,6 +140,22 @@ public void setInstitute(String institute) {
this.institute = SanitizationHelper.sanitizeTitle(institute);
}

public AccountRequestStatus getStatus() {
return this.status;
}

public void setStatus(AccountRequestStatus status) {
this.status = status;
}

public String getComments() {
return this.comments;
}

public void setComments(String comments) {
this.comments = comments;
}

public Instant getRegisteredAt() {
return this.registeredAt;
}
Expand Down Expand Up @@ -166,8 +194,8 @@ public int hashCode() {
@Override
public String toString() {
return "AccountRequest [id=" + id + ", registrationKey=" + registrationKey + ", name=" + name + ", email="
+ email + ", institute=" + institute + ", registeredAt=" + registeredAt + ", createdAt=" + getCreatedAt()
+ ", updatedAt=" + updatedAt + "]";
+ email + ", institute=" + institute + ", status=" + status + ", comments=" + comments
+ ", registeredAt=" + registeredAt + ", createdAt=" + getCreatedAt() + ", updatedAt=" + updatedAt + "]";
}

public String getRegistrationUrl() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package teammates.ui.webapi;

import teammates.common.datatransfer.AccountRequestStatus;
import teammates.common.exception.InvalidParametersException;
import teammates.common.util.EmailWrapper;
import teammates.storage.sqlentity.AccountRequest;
Expand All @@ -20,11 +21,14 @@ public JsonResult execute()
String instructorName = createRequest.getInstructorName().trim();
String instructorEmail = createRequest.getInstructorEmail().trim();
String instructorInstitution = createRequest.getInstructorInstitution().trim();
// TODO: This is a placeholder. It should be obtained from AccountCreateRequest, in a separate PR.
String comments = "PLACEHOLDER";

AccountRequest accountRequest;

try {
accountRequest = sqlLogic.createAccountRequest(instructorName, instructorEmail, instructorInstitution);
accountRequest = sqlLogic.createAccountRequest(instructorName, instructorEmail, instructorInstitution,
AccountRequestStatus.PENDING, comments);
} catch (InvalidParametersException ipe) {
throw new InvalidHttpRequestBodyException(ipe);
}
Expand Down
Loading
Loading