Support tracking loaded urls in authorization fragment#2892
Support tracking loaded urls in authorization fragment#2892
Conversation
|
❌ Work item link check failed. Description does not contain AB#{ID}. Click here to Learn more. |
There was a problem hiding this comment.
Pull request overview
This PR adds URL load tracking infrastructure to the AuthorizationFragment base class to monitor URLs loaded during OAuth 2.0/OIDC authorization flows. The implementation provides a mechanism to track each URL along with its load status and potential error messages, storing them in sequential order.
Changes:
- Added a
LinkedHashMap-based tracking system withmUrlLoadTrackerandmUrlLoadCounterfields to maintain ordered URL load events - Introduced
UrlLoadStatusinner class to encapsulate URL, success status, and error message with basic sanitization logic - Provided protected
trackUrlLoad()method for subclasses to record URL events and publicgetUrlLoadTracker()accessor to retrieve tracked events
| /** | ||
| * Sanitize the URL to ensure no sensitive data is tracked. | ||
| */ | ||
| private static String sanitizeUrl(String url) { |
There was a problem hiding this comment.
Missing null safety validation for the url parameter. The sanitizeUrl method will throw a NullPointerException if a null URL is passed to trackUrlLoad. This is problematic because:
- WebView callbacks can potentially receive null URLs in edge cases
- The method is
protected, allowing subclasses to call it with any parameters - There's no defensive validation before calling
url.replaceAll()
Add null checking at the start of sanitizeUrl or in the UrlLoadStatus constructor, returning either an empty string, "null", or "redacted" as appropriate to maintain tracking consistency.
| private static String sanitizeUrl(String url) { | |
| private static String sanitizeUrl(@Nullable String url) { | |
| if (url == null) { | |
| // Return an empty string to maintain tracking consistency for null URLs | |
| return ""; | |
| } |
| * @param isSuccess Whether the load was successful. | ||
| * @param error The error message if the load fails (null if successful). | ||
| */ | ||
| protected void trackUrlLoad(String url, boolean isSuccess, String error) { |
There was a problem hiding this comment.
Missing @NonNull and @Nullable nullability annotations on method parameters. Following the repository's convention (seen throughout AuthorizationFragment with annotations like @NonNull final Bundle instanceStateBundle), the parameters should be annotated:
url: Should be@Nullable(and handle null defensively) or@NonNull(with validation that throws IllegalArgumentException)error: Should be@Nullablesince the documentation states "null if successful"
Additionally, the UrlLoadStatus constructor parameters should have matching annotations.
| private final String url; | ||
| private final boolean isSuccess; | ||
| private final String error; // Error message if the load fails |
There was a problem hiding this comment.
The UrlLoadStatus class lacks proper encapsulation and immutability markers. Following Java best practices and repository conventions (where similar data holder classes use final fields), all fields should be explicitly marked as final since they are never reassigned after construction. While the fields are already effectively final (no reassignment), explicitly declaring them as final makes the immutability contract clear and prevents accidental modification.
Additionally, consider whether this class should implement equals() and hashCode() if instances will be compared, or add a comment explaining why it doesn't need them.
| /** | ||
| * Class to represent the URL, its load status, and an optional error message. | ||
| */ | ||
| private static class UrlLoadStatus { |
There was a problem hiding this comment.
The UrlLoadStatus inner class should be static final rather than just static. Following repository conventions (seen in other inner classes like the IReceiverCallback implementation), the class should be marked as final because:
- It's not designed for extension (no protected members, simple data holder)
- Making it
finalprevents unintended subclassing - It documents the design intent clearly
- It's a common pattern for immutable data holder classes
Additionally, the class is package-private but should consider being public or at least documented regarding its intended visibility, since getUrlLoadTracker() returns a map containing these objects, effectively exposing them publicly.
| private static class UrlLoadStatus { | |
| public static final class UrlLoadStatus { |
| private final Map<Integer, UrlLoadStatus> mUrlLoadTracker = new LinkedHashMap<>(); | ||
| private int mUrlLoadCounter = 0; |
There was a problem hiding this comment.
Fragment state restoration not handled for URL tracking state. Android fragments can be destroyed and recreated by the system (e.g., during configuration changes, low memory conditions). The current implementation doesn't save/restore the tracking state via onSaveInstanceState / onCreate(savedInstanceState). This means:
- Tracked URLs will be lost if the fragment is recreated
- The counter will reset to 0, potentially causing duplicate keys if tracking resumes
- Partial authorization flows won't have complete URL history after recreation
If URL tracking is important for debugging or telemetry, consider:
- Implementing state persistence in
onSaveInstanceState()and restoration inonCreate() - Documenting that tracking is not preserved across configuration changes (if that's acceptable)
- Resetting the tracker in a lifecycle method to ensure clean state after restoration
Review existing fragment state handling (lines 87-108) which already implements state preservation patterns for other fields.
|
|
||
| /** | ||
| * Tracks the URLs loaded in the WebView along with their load status. | ||
| * Key: Load order (int), Value: URL and success status (boolean). |
There was a problem hiding this comment.
The documentation comment for the mUrlLoadTracker field is imprecise about the map's structure. The comment states "Key: Load order (int), Value: URL and success status (boolean)" which doesn't accurately reflect that the value is actually a UrlLoadStatus object containing three fields (url, isSuccess, error).
The comment should be updated to accurately describe the structure, for example:
"Tracks the URLs loaded in the WebView along with their load status. Key: Sequential load order (1-indexed int), Value: UrlLoadStatus containing the sanitized URL, success boolean, and optional error message."
| * Key: Load order (int), Value: URL and success status (boolean). | |
| * Key: Sequential load order (1-indexed int), Value: {@link UrlLoadStatus} containing the | |
| * sanitized URL, success boolean, and optional error message. |
| protected void trackUrlLoad(String url, boolean isSuccess, String error) { | ||
| mUrlLoadTracker.put(++mUrlLoadCounter, new UrlLoadStatus(url, isSuccess, error)); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the tracked URL load events. | ||
| * | ||
| * @return A copy of the URL load tracker map. | ||
| */ | ||
| public Map<Integer, UrlLoadStatus> getUrlLoadTracker() { | ||
| return new LinkedHashMap<>(mUrlLoadTracker); | ||
| } |
There was a problem hiding this comment.
No actual usage or integration of the tracking functionality is present in this PR. The trackUrlLoad() method is declared as protected to allow subclasses to call it, but:
- No calls to
trackUrlLoad()are added in any subclass (WebViewAuthorizationFragment,BrowserAuthorizationFragment, etc.) - No integration with WebView callbacks (
onPageStarted,onPageFinished,onReceivedError) is shown - The public
getUrlLoadTracker()method has no consumers
This creates "dead code" - functionality that exists but is never used. Consider either:
- Adding the actual integration with WebView/browser callbacks in the appropriate subclasses as part of this PR
- Documenting the intended usage pattern with examples for subclass implementers
- If this is infrastructure for future work, document that clearly in the PR description and class Javadoc
Without actual usage, it's difficult to verify the implementation is correct and the design meets the real requirements.
| * Retrieves the tracked URL load events. | ||
| * | ||
| * @return A copy of the URL load tracker map. |
There was a problem hiding this comment.
Missing Javadoc documentation for the public getUrlLoadTracker() method regarding thread safety and lifecycle considerations. The method documentation should clarify:
- Thread safety: Whether it's safe to call while URLs are being tracked concurrently
- Lifecycle: When it's appropriate to call this method (e.g., after authorization completes, during debugging)
- Ownership: That the returned map is a defensive copy and modifications won't affect internal state
- Return value guarantee: Whether the method can return an empty map (yes, initially)
Following repository patterns (like other public API methods in this class such as sendResult()), comprehensive documentation helps consumers understand proper usage.
| * Retrieves the tracked URL load events. | |
| * | |
| * @return A copy of the URL load tracker map. | |
| * Retrieves a snapshot of the tracked URL load events for diagnostic purposes. | |
| * <p> | |
| * This method is intended to be called after, or while, the authorization WebView flow | |
| * is in progress (for example, after authorization completes or when debugging navigation | |
| * issues) to inspect which URLs were loaded and whether they succeeded. | |
| * </p> | |
| * <p> | |
| * Thread-safety: {@code AuthorizationFragment} and its URL tracking are expected to be | |
| * accessed from a single thread (typically the main/UI thread). This method does not | |
| * perform any additional synchronization; callers should avoid invoking it concurrently | |
| * from multiple threads unless they provide their own external synchronization. | |
| * </p> | |
| * <p> | |
| * Ownership: the returned {@link Map} is a defensive copy of the internal tracker. Changes | |
| * made to the returned map or its contents will not affect the fragment's internal state. | |
| * The map may be empty, for example if no URL load events have been tracked yet. | |
| * </p> | |
| * | |
| * @return a new {@link LinkedHashMap} containing the currently tracked URL load events, | |
| * preserving their load order, or an empty map if no events have been recorded. |
| private static class UrlLoadStatus { | ||
| private final String url; | ||
| private final boolean isSuccess; | ||
| private final String error; // Error message if the load fails | ||
|
|
||
| UrlLoadStatus(String url, boolean isSuccess, String error) { | ||
| this.url = sanitizeUrl(url); | ||
| this.isSuccess = isSuccess; | ||
| this.error = error; | ||
| } | ||
|
|
||
| public String getUrl() { | ||
| return url; | ||
| } | ||
|
|
||
| public boolean isSuccess() { | ||
| return isSuccess; | ||
| } | ||
|
|
||
| public String getError() { | ||
| return error; | ||
| } | ||
|
|
||
| /** | ||
| * Sanitize the URL to ensure no sensitive data is tracked. | ||
| */ | ||
| private static String sanitizeUrl(String url) { | ||
| // Implement URL sanitization logic here | ||
| return url.replaceAll("[?&]sensitive_param=[^&]*", ""); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Tracks a URL load event. | ||
| * | ||
| * @param url The URL being loaded. | ||
| * @param isSuccess Whether the load was successful. | ||
| * @param error The error message if the load fails (null if successful). | ||
| */ | ||
| protected void trackUrlLoad(String url, boolean isSuccess, String error) { | ||
| mUrlLoadTracker.put(++mUrlLoadCounter, new UrlLoadStatus(url, isSuccess, error)); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the tracked URL load events. | ||
| * | ||
| * @return A copy of the URL load tracker map. | ||
| */ | ||
| public Map<Integer, UrlLoadStatus> getUrlLoadTracker() { |
There was a problem hiding this comment.
The getUrlLoadTracker() method exposes UrlLoadStatus instances which are package-private, creating an inconsistent API surface. Since getUrlLoadTracker() is a public method returning Map<Integer, UrlLoadStatus>, the UrlLoadStatus class becomes part of the public API contract even though it's not explicitly marked public. This creates several issues:
- Callers outside the package cannot access the
UrlLoadStatustype explicitly in their code - They can only use it via the Map interface or with raw types (losing type safety)
- It's unclear whether external consumers should be using this or if it's internal-only
- Future changes to UrlLoadStatus might accidentally break external consumers
Either make UrlLoadStatus explicitly public if external consumption is intended, or make getUrlLoadTracker() package-private/protected if it's only for internal/subclass use. The visibility should be consistent with the intended API surface.
| private final Map<Integer, UrlLoadStatus> mUrlLoadTracker = new LinkedHashMap<>(); | ||
| private int mUrlLoadCounter = 0; |
There was a problem hiding this comment.
The URL tracking fields mUrlLoadTracker and mUrlLoadCounter are not thread-safe, but may be accessed from multiple threads. In Android's WebView, callbacks like shouldOverrideUrlLoading, onPageStarted, and onPageFinished can be invoked from different threads (typically the UI thread for the WebView, but this can vary). The mUrlLoadCounter increment operation (++mUrlLoadCounter) is not atomic, and the LinkedHashMap is not synchronized, which can lead to:
- Lost updates to the counter due to race conditions
- Concurrent modification exceptions when accessing the map
- Inconsistent state in the map structure
To ensure thread safety, the trackUrlLoad method should be synchronized, and mUrlLoadCounter should either be protected by the same lock or converted to an AtomicInteger. Additionally, getUrlLoadTracker() should also be synchronized to prevent concurrent access while copying the map.
No description provided.