Skip to content

Adds a model for apps to define credentials#1497

Merged
shubham1g5 merged 3 commits intomasterfrom
credentialModel
Sep 12, 2025
Merged

Adds a model for apps to define credentials#1497
shubham1g5 merged 3 commits intomasterfrom
credentialModel

Conversation

@shubham1g5
Copy link
Copy Markdown
Contributor

@shubham1g5 shubham1g5 commented Aug 28, 2025

Product Description

https://dimagi.atlassian.net/browse/CCCT-1339

Adds model for credentials, spec

Special deploy instructions

  • This PR can be deployed after merge with no further considerations.

Rollback instructions

  • This PR can be reverted after deploy with no further considerations.

Review

  • The set of people pinged as reviewers is appropriate for the level of risk of the change.

Duplicate PR

Automatically duplicate this PR as defined in contributing.md.

Summary by CodeRabbit

  • New Features

    • Added credential support to app profiles, enabling definition of multiple credentials (level and type) within profile features.
    • Profiles now parse and validate credentials, rejecting entries missing required attributes.
    • Credentials are preserved through import/export to ensure consistent behavior across sessions.
  • Tests

    • Added unit tests and sample profile data to verify credential parsing and validation.

cross-request: dimagi/commcare-android#3327

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 28, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a new Credential model, extends Profile to store and serialize a list of credentials, updates ProfileParser to parse a <credentials> section with <credential> entries, and introduces tests and test data validating credential parsing and serialization behavior.

Changes

Cohort / File(s) Summary
New credential model
src/main/java/org/commcare/suite/model/Credential.java
Adds Credential class with level and type, implements Externalizable with read/write, getters, and toString().
Profile model updates
src/main/java/org/commcare/suite/model/Profile.java
Adds Vector<Credential> credentials, initializes in constructor, adds getters/setters, and includes credentials in externalization (read/write).
XML parsing for credentials
src/main/java/org/commcare/xml/ProfileParser.java
Adds parsing of <credentials> and <credential> tags, validates level and type attributes, constructs Credential instances, and assigns to Profile.
Unit tests
src/test/java/org/commcare/backend/suite/model/test/ProfileTests.java
Adds testCredentialsParse verifying parsed credentials match expected values.
Test resources
src/test/resources/basic_profile.ccpr
Updates profile XML to include a <credentials> block with two <credential> entries.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Test as Test
  participant Parser as ProfileParser
  participant XML as basic_profile.ccpr
  participant Profile as Profile
  participant Cred as Credential

  Test->>Parser: parse(profile XML)
  Parser->>XML: read <features>
  XML-->>Parser: <credentials>...<credential level,type/>...</credentials>

  rect rgba(200,235,255,0.3)
  note right of Parser: New: credentials parsing
  loop for each <credential/>
    Parser->>Cred: new Credential(level, type)
    Parser-->>Parser: collect in list
  end
  end

  Parser->>Profile: setCredentials(list)
  Parser-->>Test: Profile
  Test->>Profile: getCredentials()
  Profile-->>Test: Vector<Credential>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I thump my paws on fresh new fields,
Two creds hop in with tidy shields—
Level, type, a proper pair,
Parsed and packed with careful care.
In profiles now they safely dwell;
I nibble tests—everything’s swell. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch credentialModel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/org/commcare/backend/suite/model/test/ProfileTests.java (1)

69-75: Extend serialization comparison to include credentials.

Current compareProfiles omits credentials; add a check to ensure round-trip integrity.

Example (outside changed lines):

// inside compareProfiles(Profile a, Profile b)
assertEquals("Mismatched credentials",
        Arrays.toString(a.getCredentials().toArray()),
        Arrays.toString(b.getCredentials().toArray()));

If you adopt equals/hashCode, prefer:

assertArrayEquals("Mismatched credentials",
        a.getCredentials().toArray(new Credential[0]),
        b.getCredentials().toArray(new Credential[0]));
🧹 Nitpick comments (4)
src/main/java/org/commcare/suite/model/Credential.java (1)

1-11: Give Credential value-type semantics (equals/hashCode) to enable robust comparisons and collection use.

This will simplify testing (no toString hacks) and future map/set usage.

Apply:

@@
 package org.commcare.suite.model;
 
 import org.javarosa.core.util.externalizable.DeserializationException;
 import org.javarosa.core.util.externalizable.ExtUtil;
 import org.javarosa.core.util.externalizable.Externalizable;
 import org.javarosa.core.util.externalizable.PrototypeFactory;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.util.Objects;
@@
     @Override
     public String toString() {
         return "Credential{" +
                 "level='" + level + '\'' +
                 ", type='" + type + '\'' +
                 '}';
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof Credential)) return false;
+        Credential that = (Credential) o;
+        return Objects.equals(level, that.level) &&
+                Objects.equals(type, that.type);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(level, type);
+    }
 }

Also applies to: 52-59

src/test/java/org/commcare/backend/suite/model/test/ProfileTests.java (1)

96-104: Use array equality instead of stringifying for comparisons.

With equals/hashCode on Credential, assert arrays directly for clearer failures.

-        assertEquals(Arrays.toString(expectedCredentials),Arrays.toString(p.getCredentials().toArray()));
+        assertArrayEquals(expectedCredentials, p.getCredentials().toArray(new Credential[0]));
src/main/java/org/commcare/xml/ProfileParser.java (1)

261-279: Include parser context in exceptions for better error diagnostics.

This matches other exceptions in this class and improves crash logs.

-                if (level == null) {
-                    throw new InvalidStructureException("No level defined for credential");
-                }
-                if (type == null) {
-                    throw new InvalidStructureException("No type defined for credential");
-                }
+                if (level == null) {
+                    throw new InvalidStructureException("No level defined for credential", parser);
+                }
+                if (type == null) {
+                    throw new InvalidStructureException("No type defined for credential", parser);
+                }
src/main/java/org/commcare/suite/model/Profile.java (1)

196-203: Defensive null-handling in setter.

Prevents later NPEs if called with null.

-    public void setCredentials(Vector<Credential> credentials) {
-        this.credentials = credentials;
-    }
+    public void setCredentials(Vector<Credential> credentials) {
+        this.credentials = (credentials != null) ? credentials : new Vector<>();
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 997b2fe and c698cd1.

📒 Files selected for processing (5)
  • src/main/java/org/commcare/suite/model/Credential.java (1 hunks)
  • src/main/java/org/commcare/suite/model/Profile.java (5 hunks)
  • src/main/java/org/commcare/xml/ProfileParser.java (3 hunks)
  • src/test/java/org/commcare/backend/suite/model/test/ProfileTests.java (2 hunks)
  • src/test/resources/basic_profile.ccpr (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/org/commcare/suite/model/Credential.java (1)
src/main/java/org/javarosa/core/util/externalizable/ExtUtil.java (1)
  • ExtUtil (18-395)
src/test/java/org/commcare/backend/suite/model/test/ProfileTests.java (1)
src/main/java/org/commcare/suite/model/Credential.java (1)
  • Credential (15-59)
src/main/java/org/commcare/suite/model/Profile.java (1)
src/main/java/org/javarosa/core/util/externalizable/ExtUtil.java (1)
  • ExtUtil (18-395)
src/main/java/org/commcare/xml/ProfileParser.java (1)
src/main/java/org/commcare/suite/model/Credential.java (1)
  • Credential (15-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: task-list-completed
  • GitHub Check: task-list-completed
🔇 Additional comments (6)
src/test/resources/basic_profile.ccpr (1)

94-98: Credentials block placement and schema look correct.

Tags, attribute names, and activation flag align with the parser’s expectations.

src/main/java/org/commcare/xml/ProfileParser.java (3)

9-9: Import addition is correct.


31-36: Constant names are consistent with XML schema and usage.


252-254: Feature wiring LGTM.

Parsing credentials and registering feature activation mirrors existing dependencies handling.

src/main/java/org/commcare/suite/model/Profile.java (2)

82-82: Initialize credentials vector — good default.


259-259: Writing credentials at the end keeps stream append-only; OK if read guard is added.

Confirm no legacy deserialization paths rely on exact field count without EOF tolerance.

Comment on lines +241 to 243
credentials = (Vector<Credential>)ExtUtil.read(in,
new ExtWrapList(Credential.class), pf);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Potential backward-compat break in externalization.

Older serialized Profiles (pre-credentials) would lack trailing bytes; reading will EOF. Guard read to default to empty when the stream ends.

-        credentials = (Vector<Credential>)ExtUtil.read(in,
-                new ExtWrapList(Credential.class), pf);
+        try {
+            credentials = (Vector<Credential>) ExtUtil.read(in, new ExtWrapList(Credential.class), pf);
+        } catch (java.io.EOFException eof) {
+            // Backward-compat: profiles serialized before credentials existed
+            credentials = new Vector<>();
+        }

Run to confirm Profiles aren’t persisted across versions (or identify call sites to test upgrade paths):


🏁 Script executed:

#!/bin/bash
# Locate any storage usage of Profile objects/keys
rg -nP -C2 '\bProfile\.STORAGE_KEY\b|"PROFILE"' 
rg -nP -C3 'IStorageUtility\s*<\s*Profile|StorageManager.*Profile|save\(.+Profile|write\(.+Profile'

Length of output: 2940


🏁 Script executed:

#!/bin/bash
sed -n '200,260p' src/main/java/org/commcare/suite/model/Profile.java

Length of output: 2819


Guard credentials deserialization for backward‐compatibility
In Profile.readExternal (src/main/java/org/commcare/suite/model/Profile.java), wrap the credentials read in a try/catch for EOFException so legacy profiles without credentials deserialize to an empty Vector<Credential>:

-        credentials = (Vector<Credential>)ExtUtil.read(in,
-                new ExtWrapList(Credential.class), pf);
+        try {
+            credentials = (Vector<Credential>) ExtUtil.read(in,
+                    new ExtWrapList(Credential.class), pf);
+        } catch (java.io.EOFException eof) {
+            // Backward‐compat: profiles serialized before credentials existed
+            credentials = new Vector<>();
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
credentials = (Vector<Credential>)ExtUtil.read(in,
new ExtWrapList(Credential.class), pf);
}
try {
credentials = (Vector<Credential>) ExtUtil.read(in,
new ExtWrapList(Credential.class), pf);
} catch (java.io.EOFException eof) {
// Backward‐compat: profiles serialized before credentials existed
credentials = new Vector<>();
}
}
🤖 Prompt for AI Agents
In src/main/java/org/commcare/suite/model/Profile.java around lines 241 to 243,
the credentials deserialization should be guarded for backward compatibility:
wrap the ExtUtil.read(...) call in a try/catch that catches EOFException and,
when caught, assigns credentials = new Vector<Credential>() so legacy profiles
without credentials deserialize to an empty list; rethrow or let other
exceptions propagate unchanged so only missing-data EOF is handled.

avazirna
avazirna previously approved these changes Aug 28, 2025
Copy link
Copy Markdown
Contributor

@avazirna avazirna left a comment

Choose a reason for hiding this comment

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

Is there a table with all potential levels and types? Or is that something yet to be determined.

@shubham1g5
Copy link
Copy Markdown
Contributor Author

Is there a table with all potential levels and types? Or is that something yet to be determined.

We do have some fixed types and levels on server but think it doesn't matter for mobile as mobile will just show those values as it is for now.

@shubham1g5
Copy link
Copy Markdown
Contributor Author

duplicate this PR c698cd1 c698cd1

Comment on lines +269 to +274
if (level == null) {
throw new InvalidStructureException("No level defined for credential");
}
if (type == null) {
throw new InvalidStructureException("No type defined for credential");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@shubham1g5 Blank check is already along with null here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added here - 17ac4a4

Comment on lines +269 to +270
if (level == null) {
throw new InvalidStructureException("No level defined for credential");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It is required to check the format of level e.g. for monthly it should be checking for MON_ACTIVE?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am keepint it free form at the moment on mobile as there is quite a bit of uncertainity regarding what all values these fields can take and we don't want to do a mobile update everytime we change the values here (given mobile is not doing any actions based on these values)

@shubham1g5 shubham1g5 merged commit 49720ce into master Sep 12, 2025
2 of 3 checks passed
@shubham1g5 shubham1g5 deleted the credentialModel branch September 12, 2025 06:24
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.

3 participants