Skip to content

[#13691] Migrate storage + logic layer from google id#13755

Open
Nsohko wants to merge 5 commits intoTEAMMATES:feat/oidcfrom
Nsohko:sai/migrate-google-id-phase-1
Open

[#13691] Migrate storage + logic layer from google id#13755
Nsohko wants to merge 5 commits intoTEAMMATES:feat/oidcfrom
Nsohko:sai/migrate-google-id-phase-1

Conversation

@Nsohko
Copy link
Copy Markdown
Contributor

@Nsohko Nsohko commented Apr 11, 2026

Part of #13691

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR is part of issue #13691 and continues the migration away from using googleId as the stable account key by introducing OIDC concepts (issuer + subject) and shifting internal lookups toward Account UUIDs.

Changes:

  • Introduces LoginIssuer (+ CRUD db/logic) to map OIDC issuer URLs to provider names.
  • Refactors SQL storage/logic APIs and tests to use accountId (UUID) instead of googleId for linking and queries.
  • Replaces Google ID field validation/tests with OIDC issuer/subject validation.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
src/test/java/teammates/test/BaseTestCase.java Updates typical Account construction to include issuer/subject/loginIdentifier.
src/test/java/teammates/storage/sqlapi/LoginIssuerDbTest.java Adds unit tests for LoginIssuerDb CRUD behaviors.
src/test/java/teammates/storage/sqlapi/AccountsDbTest.java Updates account tests to use UUID-based APIs; adds null-delete coverage.
src/test/java/teammates/sqllogic/core/UsersLogicTest.java Updates reset flows to accountId and adds tests for attaching accounts to instructors.
src/test/java/teammates/sqllogic/core/DataBundleLogicTest.java Updates data bundle deletion assertions to delete accounts by UUID.
src/test/java/teammates/sqllogic/core/AccountsLogicTest.java Updates delete/join tests for UUID-based account APIs and new join flow.
src/test/java/teammates/common/util/FieldValidatorTest.java Replaces Google ID validation tests with OIDC issuer/subject tests.
src/main/java/teammates/storage/sqlentity/User.java Replaces getGoogleId helper with getAccountId helper.
src/main/java/teammates/storage/sqlentity/LoginIssuer.java Adds new SQL entity for issuer→provider mapping.
src/main/java/teammates/storage/sqlentity/Account.java Refactors Account to store LoginIssuer, OIDC subject, and a loginIdentifier; adds uniqueness constraint.
src/main/java/teammates/storage/sqlapi/UsersDb.java Refactors user lookup helpers from googleId to accountId; updates search to use loginIdentifier.
src/main/java/teammates/storage/sqlapi/LoginIssuerDb.java Adds persistence layer for LoginIssuer.
src/main/java/teammates/storage/sqlapi/AccountsDb.java Removes googleId natural-id lookup; updates create logic to UUID-based access.
src/main/java/teammates/sqllogic/core/UsersLogic.java Refactors joins/resets and helper methods to accountId; adds attach-account helper.
src/main/java/teammates/sqllogic/core/LoginIssuerLogic.java Adds logic wrapper for LoginIssuerDb.
src/main/java/teammates/sqllogic/core/LogicStarter.java Wires LoginIssuerLogic into dependency initialization.
src/main/java/teammates/sqllogic/core/DataBundleLogic.java Updates account deletion in bundle removal to UUID-based deletion.
src/main/java/teammates/sqllogic/core/CoursesLogic.java Updates course creation and student course listing to accountId-based APIs.
src/main/java/teammates/sqllogic/core/AccountsLogic.java Refactors delete/join flows to accountId and delegates instructor linking to UsersLogic.
src/main/java/teammates/sqllogic/api/Logic.java Updates public SQL logic API surface to accountId + adds LoginIssuer APIs.
src/main/java/teammates/common/util/SanitizationHelper.java Replaces googleId sanitizer with loginIdentifier sanitizer.
src/main/java/teammates/common/util/HibernateUtil.java Registers LoginIssuer as a managed Hibernate entity.
src/main/java/teammates/common/util/FieldValidator.java Adds OIDC issuer/subject validation; removes Google ID validation.
src/main/java/teammates/common/util/Const.java Adds OIDC-related constants used by validation.
src/main/java/teammates/common/datatransfer/OidcProviderNameType.java Adds provider name enum for LoginIssuer.
Comments suppressed due to low confidence (1)

src/main/java/teammates/storage/sqlentity/Account.java:159

  • Account#getInvalidityInfo does not validate required linkage fields like loginIssuer (non-null) or loginIdentifier (non-null), so Account.isValid() may return true while persistence fails due to NOT NULL / FK constraints. Consider adding explicit validation for loginIssuer (non-null and/or loginIssuer.isValid()) and loginIdentifier (non-null/length/format as appropriate) to keep errors consistent with other entities.
    @Override
    public List<String> getInvalidityInfo() {
        List<String> errors = new ArrayList<>();
        addNonEmptyError(FieldValidator.getInvalidityInfoForOidcSubject(subject), errors);
        addNonEmptyError(FieldValidator.getInvalidityInfoForPersonName(name), errors);
        addNonEmptyError(FieldValidator.getInvalidityInfoForEmail(email), errors);

        return errors;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@iZUMi-kyouka iZUMi-kyouka Apr 13, 2026

Choose a reason for hiding this comment

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

Issuer scheme is always HTTPS as per the spec. So what's the use of this constant


return null;
}
} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add newline at EOF

&& !issuerUri.isOpaque();

if (!isValid) {
return getPopulatedErrorMessage(OIDC_ISSUER_ERROR_MESSAGE, issuer, OIDC_ISSUER_FIELD_NAME,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can this be simplified to reduce duplication?

* contains scheme, host, and optionally, port number and path components and no query or fragment components.
*
* @return An explanation of why the {@code issuer} is not acceptable.
* Returns an empty string if the {@code issuer} is acceptable.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wrong comment? "returns.."

/**
* Updates an issuer to provider name mapping.
*/
public LoginIssuer updateLoginIssuer(LoginIssuer loginIssuer)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When would we need this? Seems unlikely that we need to update a mapping

* privileges.
*
* @param instructorGoogleId the Google ID of the instructor creating the
* @param instructorAccountId the Account ID of the instructor creating the
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think account should not be capitalised

* Parameters regkey and googleId are non-null.
* Validates that an existing instructor can be linked to an existing account, then performs the link.
*
* <p>This is a lower-level helper flow than the registration-key-based join path.</p>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need this comment?

if (googleId == null) {
throw new InvalidParametersException("Instructor's googleId cannot be null");
if (accountId == null) {
throw new InvalidParametersException("Instructor's account Id cannot be null");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

accountId

validateJoinCourseRequest(googleId, instructor);
return usersLogic.joinCourseForInstructor(googleId, instructor);
validateInstructorAccountLinkRequest(accountId, instructor);
return usersLogic.attachAccountToInstructor(accountId, instructor);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I personally think "link" sounds better


// Update the googleId of the student entity for the instructor which was created from sample data.
// If this instructor already has a student record in the same course
// without an existing account (e.g. from sample/imported data),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not possible due to unique constrant in schema

Comment on lines +22 to +24
if (providerName == null) {
return null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe can use StringHelper.isEmpty for null check


public static final String GOOGLE_ID_FIELD_NAME = "Google ID";
public static final int GOOGLE_ID_MAX_LENGTH = 254;
public static final String OIDC_ISSUER_FIELD_NAME = "OIDC issuer";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should there be a max length for issuer too?

}
} else {
// Check if this Google ID has already joined this course
// Check if this Account ID has already joined this course
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The A in Account should be lowercase to be uniform with UserInfo

Comment on lines 156 to 165
if (instructorForKey.isRegistered()) {
if (instructorForKey.getGoogleId().equals(googleId)) {
Account existingAccount = accountsDb.getAccountByGoogleId(googleId);
if (instructorForKey.getAccountId().equals(accountId)) {
Account existingAccount = accountsDb.getAccount(accountId);
if (existingAccount != null) {
throw new EntityAlreadyExistsException("Instructor has already joined course");
}
} else {
throw new EntityAlreadyExistsException("Instructor has already joined course");
}
} else {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe can consider guard clause to avoid deep nesting here.

* Returns a list of {@link Course} for all courses a given student is enrolled in.
*
* @param googleId The Google ID of the student
* @param accountId The Account ID of the student
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same here


if (getAccountByGoogleId(account.getGoogleId()) != null) {
if (getAccount(account.getId()) != null ||
getAccountByLoginIssuerAndSub(account.getLoginIssuer(), account.getOidcSubject())!= null) {
Copy link
Copy Markdown
Contributor

@TobyCyan TobyCyan Apr 14, 2026

Choose a reason for hiding this comment

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

Should have a space between operand and operator at account.getOidcSubject())!=

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants