Skip to content
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

[I18n] Compile html in angular directive #23684

Conversation

LeanidShutau
Copy link
Contributor

@LeanidShutau LeanidShutau commented Oct 2, 2018

Resolves #23359

We need to compile html in i18n directive to be able to pass html with angular directive via values, for example

<p>
  <span
    i18n-id="test.test"
    i18n-default-message="I just check {justToCheck}"
    i18n-values="{ justToCheck: '<strong ng-click=&quot;checkDirectivesAreCompiled()&quot;>click me!</strong>' }"
  ></span>
</p>

It's not secure, so we need to sanitize or escape html in defeultMessage or in values, that are not supposed to contain potentially dangerous html code. One of the solutions is to use unsafe_ prefix in some values keys and sanitize/escape html in defaultMessage and all other values.

@LeanidShutau LeanidShutau added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Project:i18n v6.5.0 labels Oct 2, 2018
@LeanidShutau LeanidShutau self-assigned this Oct 2, 2018
})
);
$compile($element.contents() as any)($scope.$parent);
Copy link
Contributor

@pavel06081991 pavel06081991 Oct 2, 2018

Choose a reason for hiding this comment

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

@azasypkin I believe now it is safe to compile html because:

  1. we have agreed not to use tags in defaultMessage.
  2. even if some vendor pass any tags, scripts or directive in defaultMessage for some language, before inserting defaultMessage to DOM we check passed string using $sanitize angular service which removes all unsafe tags like script and directives.
    For example we have such text in defaultMessage:
<span>hello</span>world
<im-dangerous-directive>I will destroy something</im-dangerous-directive>
<script>document.write('ha-ha')</script>

after using $sanitize service we will get:

<span>hello</span>world

@pavel06081991
Copy link
Contributor

pavel06081991 commented Oct 2, 2018

@azasypkin this merge request is necessary for us to be able to pass angular directives via values. Now we also can pass directives but they do not work because they need to be compiled.

To check that all works as expected you can:
in file src\core_plugins\kibana\public\dashboard\dashboard_app.html
replace for example this code:

<p class="kuiText kuiVerticalRhythm">
  Click the <a kbn-accessible-click class="kuiButton kuiButton--primary kuiButton--small" ng-click="showAddPanel()" aria-label="Add visualization" data-test-subj="emptyDashboardAddPanelButton">Add</a> button in the menu bar above to add a visualization to the dashboard. <br/>If you haven't set up any visualizations yet, <a class="kuiLink" href="#/visualize">visit the Visualize app</a> to create your first visualization.
</p>

with this code:

<p>
  <span i18n-id="test.test"
    i18n-default-message="I just check {justToCheck}"
    i18n-values="{ justToCheck: '<strong ng-click=&quot;checkDirectivesAreCompiled()&quot;>click me!</strong>' }">
  </span>
</p>

and in file src\core_plugins\kibana\public\dashboard\index.js
find line .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { and make it look like this:

.when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {
  template: dashboardTemplate,
  controller: ($scope) => {
    $scope.checkDirectivesAreCompiled = () => {
      alert('I am working!');
    };
  },
  resolve: {

open dashboard page.
If you click on 'click me!' text and see alert window, then it works because angular directive ng-click now works when passed via values.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@azasypkin
Copy link
Member

ack: Looking into this one at the monent

@LeanidShutau LeanidShutau requested review from pavel06081991 and azasypkin and removed request for azasypkin October 4, 2018 07:18
Copy link
Contributor

@pavel06081991 pavel06081991 left a comment

Choose a reason for hiding this comment

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

LGTM. Checked it works as expected.

@LeanidShutau LeanidShutau force-pushed the feature/compile-html-in-i18n-directive branch from 2bc299e to 6486b93 Compare October 5, 2018 14:49
@elasticmachine
Copy link
Contributor

💔 Build Failed

@LeanidShutau
Copy link
Contributor Author

retest

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@LeanidShutau
Copy link
Contributor Author

retest

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@LeanidShutau
Copy link
Contributor Author

retest

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@@ -38,20 +42,39 @@ export function i18nDirective(i18n: I18nServiceType): IDirective<I18nScope> {
link($scope, $element) {
if ($scope.values) {
$scope.$watchCollection('values', () => {
setHtmlContent($element, $scope, i18n);
setHtmlContent($element, $scope, $sanitize, i18n);
$compile($element.contents() as any)($scope.$parent);
Copy link
Member

Choose a reason for hiding this comment

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

issue: I'm still afraid of this "global" compile.... with this even $sanitize'ed variable without usafe_ can be pretty harmful, imagine the case that looks like this (help link that is displayed somewhere else on this page will lead user to the attacker site):

  <span i18n-id="kbn.management.someId"
        i18n-default-message="Some text that seems safe: {val}"
        i18n-values="{ val: '\{\{controller.helpLink=\'http://attacker-site/\';\'value\'\}\}' }">
  </span>

Is there any way we can compile just unsafe_ pieces? Can you paste a couple of real use cases? If it's only about directives (e.g. <a ng-href\ng-click), we can probably mark all these elements with directive with some custom data-unsafe-i18n data attribute or class and then query only these things and compile.

I know it's becoming complex, but I feel uncomfortable with how it's currently. Basically we have 3 cases (sorted by popularity):

  1. Simple default message + simple values, in this case we should just use element.text(i18n.translate(...)) - text will escape everything for us.
  2. Simple default message + values with HTML (html_ prefix) - in this case we have to use element.html, so we should escape default message (let's try lodash.escape instead of manual solution, we have several lodash.* packages in Kibana already anyway) + escape ordinary values (without html_ prefix) + $sanitize html_ values
  3. Simple default message + unsafe values (unsafe_ prefix_) - in this case we also have to use element.html, so again we should escape default message + escape ordinary values + $sanitize html_ values. And once we inserted them into DOM, we should call $compile for nodes with data-unsafe-i18n (or something like this).

Am I over-engineering this (or missing something) @spalger @LeanidShutau?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any way we can compile just unsafe_ pieces? Can you paste a couple of real use cases? If it's only about directives (e.g. <a ng-href\ng-click), we can probably mark all these elements with directive with some custom data-unsafe-i18n data attribute or class and then query only these things and compile.

Good point. I believe we can do it. It looks like in this case we do not need to use unsafe_ prefix for values' property names.

Copy link
Contributor

Choose a reason for hiding this comment

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

I have to be honest, I don't have enough context to say if this is over-engineering or a comprehensive solution. Since I've been asked to review this and it seems like the overall direction is still in flux I'd really like to chat about what's going on here and how/why we're doing things this way. The PR description and the linked issue have basically no details and I just don't know enough to review this...

values: $scope.values,
defaultMessage: $scope.defaultMessage,
values,
defaultMessage: ($scope.defaultMessage || '').replace(/\</g, '&lt;').replace(/\>/g, '&gt;'),
Copy link
Member

Choose a reason for hiding this comment

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

question: why do we need || ''? I believe we shouldn't ever have empty default message?

@LeanidShutau
Copy link
Contributor Author

We don't need angular directive compiling any more. $sanitize part was implemented in separate PR: #24830

Issue has been solved by refactoring:

<p class="kuiText kuiVerticalRhythm">
  <span
    i18n-id="kbn.dashboard.addVisualizationDescription1"
    i18n-default-message="Click the "
    i18n-context="Part of composite label kbn.dashboard.addVisualizationDescription1 + {link} + kbn.dashboard.addVisualizationDescription2"
  >
  </span>
  <a
    i18n-id="kbn.dashboard.addVisualizationLink"
    i18n-default-message="Add"
    kbn-accessible-click class="kuiButton kuiButton--primary kuiButton--small"
    ng-click="showAddPanel()"
    aria-label="{{ 'kbn.dashboard.addVisualizationLinkAria...' | i18n: { defaultMessage: 'Add visualization' }}"
    data-test-subj="emptyDashboardAddPanelButton">
  </a>
  <span
    i18n-id="kbn.dashboard.addVisualizationDescription2"
    i18n-default-message=" button in the menu bar above to add a visualization to the dashboard. {br}If you haven't set up any visualizations yet, {visitVisualizeAppLink} to create your first visualization."
    i18n-values="{
        br: '<br/>',
        visitVisualizeAppLink: '<a class=\'kuiLink\' href=\'#/visualize\'>' + visitVisualizeAppLinkText + '</a>'}"
    i18n-context="Part of composite label kbn.dashboard.addVisualizationDescription1 + {link} + kbn.dashboard.addVisualizationDescription2"
  >
  </span>
</p>

This PR can be reopened in the future, if needed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discuss Project:i18n Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v6.6.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

find solution for angular how to pass html containing angular directives via values param
6 participants