Skip to content

Conversation

@LRNcardozoWDF
Copy link
Member

@LRNcardozoWDF LRNcardozoWDF commented Nov 11, 2025

🎟️ Tracking

PM-19305

📔 Objective

Allow organizations to enforce session timeout policy in the iOS app.

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@LRNcardozoWDF LRNcardozoWDF requested review from a team and matt-livefront as code owners November 11, 2025 03:32
@claude
Copy link

claude bot commented Nov 11, 2025

Claude finished @LRNcardozoWDF's task —— View job


Code Review Complete

Summary of Changes Since Last Review

The latest commits (b2acdf2, 9a80efe, 69a91ff) addressed several findings from the previous review:

  • Finding 1 (Documentation error): ✅ Fixed in b2acdf2 - corrected SessionTimeoutPolicy initializer docs
  • Finding 2 (Pluralization): ✅ Fixed in 9a80efe - proper .stringsdict pluralization already exists for XMinutes and XHours
  • Finding 6 (Missing tests): ✅ Fixed in 69a91ff - added comprehensive SessionTimeoutType tests
  • Finding 7 (Complex didSet logic): ✅ Fixed in b2acdf2 - extracted to computeAvailableTimeoutOptions() method

Findings 3, 4, and 8 were marked as working as intended by the author with valid explanations.

Changes Overview

This PR implements organization-enforced session timeout policies across 17 files (693 additions, 96 deletions). The implementation correctly follows the established architecture patterns with proper separation between Core and UI layers.


Critical Issues

None found. The previous critical issues have been addressed.


Suggested Improvements

Finding 9: Potential nil handling inconsistency in SyncService

💭 In SyncService.swift:464-467, when policy type is .onAppRestart or .never, the code sets vault timeout to the user's existing value without validation. While the author confirmed this is intentional, the logic could be clearer:

if type == SessionTimeoutType.onAppRestart || type == SessionTimeoutType.never {
    try await stateService.setVaultTimeout(
        value: timeoutValue,  // User's current value, unchanged
    )
}

The call to setVaultTimeout with the unchanged value serves no functional purpose. Consider either:

  1. Removing this branch entirely (no-op)
  2. Adding a comment explaining why we explicitly re-set the same value
  3. Only calling setVaultTimeout when the value actually needs updating
Rationale

Setting a value to itself can confuse future maintainers. If this is intentional for triggering side effects (e.g., observers, notifications), document it clearly. Otherwise, eliminate the no-op.


Finding 10: Missing documentation for public property

⚠️ SettingsPickerField.swift:27 - The @State var showTimePicker property lacks DocC documentation. Per project guidelines, all public properties require documentation (except protocol implementations and mocks).

/// Whether or not to show the date picker.
@SwiftUI.State var showTimePicker = false

While this has a comment on line 26, it should use /// DocC format for consistency with project standards.


Finding 11: Test coverage gaps in SettingsPickerField

♻️ SettingsPickerField.swift has 0% patch coverage (12 missing lines per Codecov). The new view component lacks:

  • Snapshot tests for visual verification
  • View inspector tests for interaction behavior

Consider adding tests in SettingsPickerFieldTests.swift to verify:

  • Time picker expansion/collapse behavior
  • Footer text display with/without policy messages
  • Accessibility label correctness

Finding 12: Incomplete edge case handling in policy enforcement

💭 PolicyService.swift:278 - The guard statement guard let policyTimeoutValue = policy[.minutes]?.intValue else { continue } silently skips policies with nil minutes. While the comment suggests this "is never null," the defensive guard indicates uncertainty.

Recommendation: Add logging when skipping a policy to aid debugging:

guard let policyTimeoutValue = policy[.minutes]?.intValue else {
    services.errorReporter.log(error: PolicyServiceError.invalidPolicyData(
        message: "Policy minutes value is nil for policy: \(policy.id)"
    ))
    continue
}

This helps diagnose misconfigured policies without failing the sync.


Finding 13: Potential integer overflow in timeout calculations

⚠️ AccountSecurityState.swift:216-218 - Computing hours and minutes from policy timeout value lacks overflow protection:

/// The policy's timeout value in hours.
var policyTimeoutHours: Int {
    policyTimeoutValue / 60  // Could overflow if policyTimeoutValue is Int.max
}

While unlikely in practice (timeout values are constrained), defensive programming suggests:

var policyTimeoutHours: Int {
    guard policyTimeoutValue > 0 else { return 0 }
    return policyTimeoutValue / 60
}

Good Practices Observed

  • Comprehensive test coverage for core business logic (95.76% patch coverage)
  • Proper use of dependency injection via ServiceContainer
  • Clean separation between policy enforcement (Core) and UI presentation (UI layer)
  • Backward compatibility with legacy servers (nil type handling)
  • Added SessionTimeoutTypeTests covering all edge cases
  • Extracted complex logic to testable methods (computeAvailableTimeoutOptions)

Action Items

  1. Optional: Address Finding 9 (no-op setVaultTimeout call) - Low priority, improves clarity
  2. Recommended: Fix Finding 10 (missing DocC for showTimePicker property)
  3. Recommended: Add test coverage for SettingsPickerField (Finding 11)
  4. Consider: Add logging for skipped policies (Finding 12)
  5. Consider: Add overflow protection in timeout calculations (Finding 13)

Overall Assessment

Strong implementation that correctly enforces session timeout policies while maintaining backward compatibility. The latest commits successfully addressed all critical findings from the previous review. The remaining suggestions are minor improvements for code clarity, documentation completeness, and test coverage.

The architecture is sound, following established patterns with proper layer separation. Security considerations are appropriately handled through the existing policy enforcement mechanisms.



// MARK: Initialization

/// Initialize `EnvironmentURLData` with the specified URLs.
Copy link

Choose a reason for hiding this comment

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

Finding 1: Copy-paste error in documentation

The documentation still references EnvironmentURLData and mentions an api parameter, but this is the SessionTimeoutPolicy initializer with completely different parameters.

Suggested change
/// Initialize `EnvironmentURLData` with the specified URLs.
/// Initialize `SessionTimeoutPolicy` with the specified values.
///
/// - Parameters:
/// - timeoutAction: The action to perform on session timeout.
/// - timeoutType: The type of session timeout.
/// - timeoutValue: The session timeout value.

"ThisSettingIsManagedByYourOrganization" = "This setting is managed by your organization.";
"YourOrganizationHasSetTheDefaultSessionTimeoutToX" = "Your organization has set the default session timeout to %1$@.";
"YourOrganizationHasSetTheDefaultSessionTimeoutToXAndY" = "Your organization has set the default session timeout to %1$@ and %2$@.";
"XMinutes" = "%1$@ minute(s)";
Copy link

Choose a reason for hiding this comment

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

⚠️ Finding 2: Missing pluralization rules

The string "XMinutes" = "%1$@ minute(s)"; uses generic "(s)" pluralization which won't work correctly for all languages. iOS supports proper pluralization via .stringsdict files.

Consider creating a Localizable.stringsdict entry:

<key>XMinutes</key>
<dict>
    <key>NSStringLocalizedFormatKey</key>
    <string>%#@minutes@</string>
    <key>minutes</key>
    <dict>
        <key>NSStringFormatSpecTypeKey</key>
        <string>NSStringPluralRuleType</string>
        <key>NSStringFormatValueTypeKey</key>
        <string>d</string>
        <key>one</key>
        <string>%d minute</string>
        <key>other</key>
        <string>%d minutes</string>
    </dict>
</dict>

The same applies to XHours if it exists.

// Only update the user's stored vault timeout value if
// their stored timeout value is > the policy's timeout value.
if timeoutValue.rawValue > value || timeoutValue.rawValue < 0 {
if type == SessionTimeoutType.onAppRestart || type == SessionTimeoutType.never {
Copy link

Choose a reason for hiding this comment

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

Finding 3: Logic inconsistency bypasses policy enforcement

When the policy type is onAppRestart or never, this code sets the timeout value to whatever the user currently has stored (timeoutValue), without any validation against the policy. This means:

  1. If a user previously had a custom timeout that violates the new policy, it won't be corrected
  2. The policy value is essentially ignored for these types

The logic should validate that the user's stored value is compatible with the policy type. For example:

  • If policy type is never, user's value should be set to .never
  • If policy type is onAppRestart, user's value should be set to .onAppRestart

Otherwise, the user could have a stored value of "5 minutes" when the policy says "never", which is inconsistent.

Copy link
Member Author

Choose a reason for hiding this comment

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

The implemented behaviour is correct, the user's value can in those cases be different than the policy since the policy type also allows those values.

state.policyTimeoutValue = policy.value
state.isTimeoutPolicyEnabled = true
state.policyTimeoutAction = policy.timeoutAction
state.policyTimeoutType = policy.timeoutType ?? SessionTimeoutType.custom
Copy link

Choose a reason for hiding this comment

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

💭 Finding 4: Unclear default handling for nil policy type

When policy.timeoutType is nil (legacy servers), this defaults to SessionTimeoutType.custom. However, the logic in PolicyService.swift:284-286 suggests that nil type should be handled differently. Is custom always the correct default for legacy servers?

Consider documenting why .custom is the appropriate fallback, or if the logic should check the policy value to determine a more appropriate type (e.g., if value matches a predefined timeout like 60 minutes, should it be .predefined instead?).

Copy link
Member Author

Choose a reason for hiding this comment

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

.custom is always the correct default for legacy servers since they will send the timeoutType as null.

Comment on lines 103 to 105
var policyTimeoutValue: Int = 0 {
didSet {
availableTimeoutOptions = SessionTimeoutValue.allCases
.filter { $0 != .never }
.filter { $0 != .onAppRestart }
.filter { $0.rawValue <= policyTimeoutValue }
availableTimeoutOptions = SessionTimeoutValue.allCases.filter { option in
Copy link

Choose a reason for hiding this comment

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

🎨 Finding 7: Complex logic in property observer reduces testability

The didSet observer contains significant filtering logic (27 lines) that modifies availableTimeoutOptions based on policyTimeoutType and policyTimeoutValue. This makes the state harder to test and understand.

Consider extracting this to a computed property or dedicated method:

var policyTimeoutValue: Int = 0

var availableTimeoutOptions: [SessionTimeoutValue] {
    computeAvailableTimeoutOptions(
        type: policyTimeoutType,
        value: policyTimeoutValue
    )
}

private func computeAvailableTimeoutOptions(
    type: SessionTimeoutType?,
    value: Int
) -> [SessionTimeoutValue] {
    // Filtering logic here
}

This approach:

  • Makes the logic independently testable
  • Clarifies the dependency on both policyTimeoutType and policyTimeoutValue
  • Removes side effects from property setters

///
/// - Parameter rawValue: The string representation of the type raw value.
///
public init(rawValue: String?) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Finding 6: Missing test coverage for SessionTimeoutType

This enum lacks dedicated unit tests, particularly for:

  1. The init(rawValue:) method with all possible string values
  2. The legacy "onSystemLock" → "onAppRestart" mapping (line 70)
  3. The default case handling (line 73) which falls back to .custom
  4. The init(value:) method with all SessionTimeoutValue cases

These initialization paths are critical for policy enforcement compatibility with legacy servers. Add tests in a new file: BitwardenKit/Core/Platform/Models/Enum/SessionTimeoutTypeTests.swift

var timeoutValue: SessionTimeoutValue?

for policy in policies {
// This is never null, but maybe it should be revised
Copy link

Choose a reason for hiding this comment

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

💭 Finding 8: Comment suggests uncertainty about business logic

The comment "This is never null, but maybe it should be revised" indicates uncertainty about whether the policy's minutes value can be null. This warrants clarification:

  1. If it truly can never be null per the API contract, remove the guard and the comment
  2. If it can be null and represents a valid state, the continue is correct but should be documented
  3. If the server contract is unclear, add error logging before continuing

This is important for policy enforcement - skipping a policy without logging could hide configuration issues.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 11, 2025

Logo
Checkmarx One – Scan Summary & Detailsa31d5b6d-3b6d-4d25-9c5d-822075d5fc88

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Nov 11, 2025

Codecov Report

❌ Patch coverage is 95.76427% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.98%. Comparing base (26c158b) to head (03bacf1).

Files with missing lines Patch % Lines
...atform/Application/Views/SettingsPickerField.swift 0.00% 12 Missing ⚠️
...ettings/AccountSecurity/AccountSecurityState.swift 96.00% 4 Missing ⚠️
...Settings/AccountSecurity/AccountSecurityView.swift 50.00% 4 Missing ⚠️
...ntSecurity/AccountSecurityView+SnapshotTests.swift 0.00% 2 Missing ⚠️
...rdenShared/Core/Vault/Services/PolicyService.swift 95.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2127      +/-   ##
==========================================
- Coverage   85.38%   83.98%   -1.41%     
==========================================
  Files        1725     1982     +257     
  Lines      145595   161404   +15809     
==========================================
+ Hits       124316   135554   +11238     
- Misses      21279    25850    +4571     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@LRNcardozoWDF LRNcardozoWDF marked this pull request as draft November 11, 2025 05:13
@LRNcardozoWDF LRNcardozoWDF force-pushed the cmcg/pm-19305-vault-timeout-policy branch from 60abeb5 to 8bb7d69 Compare November 13, 2025 02:06
@LRNcardozoWDF LRNcardozoWDF marked this pull request as ready for review November 18, 2025 11:06
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.

2 participants