-
Notifications
You must be signed in to change notification settings - Fork 142
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
✨ [RUMF-998] introduce the initialPrivacyLevel configuration option #1004
✨ [RUMF-998] introduce the initialPrivacyLevel configuration option #1004
Conversation
The goal is to simplify the Privacy Level typings by removing the distinction between "Internal" and non-"Internal" levels. This is a first step: it introduces a new function that focuses on handling the "MASK_FORMS_ONLY" level. This way, we don't have to worry whether "MASK" or "MASK_FORMS_ONLY" is transmitted to children.
This commit removes the "remap" function and associated. The `getInternalNodePrivacyLevel` becomes `getNodePrivacyLevel` and can return any privacy level. This makes typings a bit less safe, but following commits will improve the situation.
Now that associated functions have been removed, we can have a single type for all privacy levels.
This change is in preparation of the initialPrivacyLevel configuration option. It changes the `getNodePrivacyLevel` paradigm a bit: instead of receiving the direct parent privacy level, it receives a default privacy level to be used if no level was found. Thus, this function now always recurse over the node ancestors. During serialization, where we know the parent privacy level, we can use the `getNodeSelfPrivacyLevel` function to avoid this behavior.
Codecov Report
@@ Coverage Diff @@
## main #1004 +/- ##
==========================================
- Coverage 89.04% 88.91% -0.13%
==========================================
Files 86 86
Lines 4115 4105 -10
Branches 946 945 -1
==========================================
- Hits 3664 3650 -14
- Misses 451 455 +4
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I can either approve or refuse this PR but it looks good.
This reduce corner cases a bit by leveraging the type to restrict some API usages.
Same as previous commit, this allows to reduce the scope of some functions by passing explicit subsets of privacy levels
To make sure the initial privacy level is correctly set everywhere, make it required on `getNodePrivacyLevel` and `serializeDocument` (and associated subfunctions).
ed2e242
to
7c0a10a
Compare
* More explicit function naming * Improved typings inference
For the overall understanding of the recorder, it might be interesting to split the observer.ts file like so for instance:
Same logic could be applied for serialize.ts:
... |
switch (privAttr) { | ||
case PRIVACY_ATTR_VALUE_ALLOW: | ||
return NodePrivacyLevelInternal.ALLOW | ||
return NodePrivacyLevel.ALLOW | ||
case PRIVACY_ATTR_VALUE_MASK: | ||
return NodePrivacyLevelInternal.MASK | ||
return NodePrivacyLevel.MASK | ||
case PRIVACY_ATTR_VALUE_MASK_FORMS_ONLY: | ||
case PRIVACY_ATTR_VALUE_INPUT_IGNORED: // Deprecated, now aliased | ||
case PRIVACY_ATTR_VALUE_INPUT_MASKED: // Deprecated, now aliased | ||
return NodePrivacyLevelInternal.MASK_FORMS_ONLY | ||
return NodePrivacyLevel.MASK_FORMS_ONLY | ||
case PRIVACY_ATTR_VALUE_HIDDEN: | ||
return NodePrivacyLevelInternal.HIDDEN | ||
return NodePrivacyLevel.HIDDEN | ||
} | ||
|
||
// But we also need to support class based privacy tagging for certain frameworks | ||
if (elNode.classList.contains(PRIVACY_CLASS_ALLOW)) { | ||
return NodePrivacyLevelInternal.ALLOW | ||
return NodePrivacyLevel.ALLOW | ||
} else if (elNode.classList.contains(PRIVACY_CLASS_MASK)) { | ||
return NodePrivacyLevelInternal.MASK | ||
return NodePrivacyLevel.MASK | ||
} else if (elNode.classList.contains(PRIVACY_CLASS_HIDDEN)) { | ||
return NodePrivacyLevelInternal.HIDDEN | ||
return NodePrivacyLevel.HIDDEN | ||
} else if ( | ||
elNode.classList.contains(PRIVACY_CLASS_MASK_FORMS_ONLY) || | ||
elNode.classList.contains(PRIVACY_CLASS_INPUT_MASKED) || // Deprecated, now aliased | ||
elNode.classList.contains(PRIVACY_CLASS_INPUT_IGNORED) // Deprecated, now aliased | ||
) { | ||
return NodePrivacyLevelInternal.MASK_FORMS_ONLY | ||
return NodePrivacyLevel.MASK_FORMS_ONLY | ||
} else if (shouldIgnoreElement(elNode)) { | ||
// such as for scripts | ||
return NodePrivacyLevelInternal.IGNORE | ||
return NodePrivacyLevel.IGNORE | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the same conditions for HTML attributes and CSS classes privacy. It could be nice not to repeat ourselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, and we would have the same priority for both classes and attributes. For example, an element with a hidden
class and a mask
attribute should be considered as hidden
(higher privacy guarantees). I'll change this in a separate PR.
@@ -182,7 +194,7 @@ export function initInputObserver(cb: InputCallback): ListenerHandler { | |||
|
|||
let inputState: InputState | |||
if (type === 'radio' || type === 'checkbox') { | |||
if (nodePrivacyLevel === NodePrivacyLevel.MASK) { | |||
if (shouldMaskNode(target, nodePrivacyLevel)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really inderstand why we need shouldMaskNode()
, if we already have getNodePrivacyLevel()
There is probably a confusion between the Mask term in shouldMaskNode()
and the NodePrivacyLevel.MASK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a comment about this, but:
shouldMaskNode
is a helper mostly aiming to unify mask-forms-only
and mask
privacy levels. In the mask
case, it is trivial: we should mask the element. In the mask-forms-only
case, we should mask the element only if it is a "form" element (or the direct parent is a form element for text nodes). Other shouldMaskNode
cases are edge cases that should not matter too much (ex: should we mask a node if it is ignored or hidden? it doesn't matter since it won't be serialized, but the case is handled anyway to be exhaustive).
It has been introduced to remove the need to have two different NodePrivacyLevel
types (Internal
and not Internal
), which was a bit obscur for me and made introducing a new type (InitialPrivacyLevel
) even more confusing. The NodePrivacyLevel
(not Internal
) was designed to unify the mask
and mask-forms-only
into a single mask
variant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get the improvement that is legitimate. But I still find it a bit confusing especially in the case bellow, where we check the hidden level and then the shouldMaskNode()
which also checks the hidden level.
browser-sdk/packages/rum/src/domain/record/privacy.ts
Lines 261 to 264 in 6837279
} else if (nodePrivacyLevel === NodePrivacyLevel.HIDDEN) { | |
// Should never occur, but just in case, we set to CENSORED_MARK. | |
textContent = CENSORED_STRING_MARK | |
} else if (shouldMaskNode(textNode, nodePrivacyLevel)) { |
However, I can consider it as good enough ;)
Also, leverage the typegard and use `node` as an element directly
I agree, we can split things up in another PR. |
Small code improvement, instead of: browser-sdk/packages/rum/src/domain/record/privacy.ts Lines 258 to 263 in 6837279
if (
isScript || // For perf reasons, we don't record script (heuristic)
nodePrivacyLevel === NodePrivacyLevel.HIDDEN // Should never occur, but just in case, we set to CENSORED_MARK.
) {
textContent = CENSORED_STRING_MARK
} |
I'd agree with the first case, |
Motivation
Allow the user to set a default privacy level for session replay
Changes
This PR is changing a bit the design of the Privacy by Default implementation #914 .
Mainly, it removes the confusing distinct types
NodePrivacyLevel
andNodePrivacyLevelInternal
, to allow simpler inheritance of the privacy level.It also make the privacy level a require argument for main recording functions, to allow passing an initial privacy level.
Finally, it implements the new configuration option publicly.
Testing
I have gone over the contributing documentation.