[#74710] show inline page link macros in editing mode#23142
Conversation
- https://community.openproject.org/work_packages/74710 - show page link as turbo frame - add route for loading the macro - update ckeditor build
|
Caution The provided work package version does not match the core version Details:
Please make sure that:
|
NobodysNightmare
left a comment
There was a problem hiding this comment.
This PR LGTM mostly.
Exclusions:
- I did not yet look at the underlying CKEditor changes, those still require a review
- I think we can remove the
providerfrom theInlinePageLinkMacroComponent
|
|
||
| alias_method :page_info_result, :model | ||
|
|
||
| attr_reader :provider |
There was a problem hiding this comment.
🟡 Is the provider used anywhere within the component?
There was a problem hiding this comment.
that is a good question :D I tried to go over my tracks again, and I think, my first iteration tried to resolve the query here instead the outside.
So, no, this is no longer needed and I can drop it.
| ->(error) { render_error_macro(view_context, error) } | ||
| ) | ||
| end | ||
| InlinePageLinkMacroComponent.new(page_info_result, provider:).render_in(view_context) |
There was a problem hiding this comment.
💚 I like the extraction of the InlinePageLinkMacroComponent
| end | ||
|
|
||
| def find_provider | ||
| @provider = Provider.find(params[:provider_id]) |
There was a problem hiding this comment.
🔴 During manual testing I found a case that's breaking stuff. My test work package contains a link with an invalid provider.
I believe what happens is that this causes a 404 when loading this component (because the find fails), resulting in a Turbo error:
Screencast.from.2026-05-11.09-39-53.webm
I think this is a case that needs to be explicitly handled here.
| return `${this.staticBase}/external_redirect?url=${encodeURIComponent(url)}`; | ||
| } | ||
|
|
||
| public wikiPageLinkMacro(providerId:number, pageIdentifier:string, turboFrameId:string) { |
There was a problem hiding this comment.
providerId:number is a wrong type annotation. In reality we pass the result of a regex match in here.
The regex only matches numbers, though this method can't know this. In fact we will pass a string and should also escape it properly.
There was a problem hiding this comment.
I was thinking about that same case, but came to a different conslusion.
The path helper service is owned by us and not exposed as a public API. Hence, we can fully control the input.
The provider id is a number in our data model, due to that fact we have both the regex and the method signature here to look for number. This is the causality I'd see here, not that the method signature is following the regex.
Same for the turbo frame id, we "define" this to be a UUID, after that, all methods consume the data as is. The only exception is the page identifier, this is external and not controlled by us. We can see that also in both locations, the regex matches any characters except some control characters, and the path helper escapes it before giving it to the UI.
There was a problem hiding this comment.
Let's start with the (hopefully) easier one to discuss:
The provider id is a number in our data model, due to that fact we have both the regex and the method signature here to look for number.
Tell me if I misunderstand TypeScript, but essentially it only works to guarantee types if both sides (caller and callee) use TypeScript, but there are no runtime checks for the types.
I am asking this, because this method is called like this (abbreviated for clarity):
const match = WIKI_PAGE_LINK_RE.exec(word);
const providerId = match[1];
const frameSrc = getOPPath(editor).wikiPageLinkMacro(providerId, "not relevant", "not relevant");Now match[1] is the result of a regex match and those are always strings. In this case it's a string that only contains the characters 0-9, but it's a string still:
typeof /([0-9]+)/.exec("123")[1]
=> "string" The error is not noticable here, because all we do so far is interpolating providerId in a string, but it is in fact not of type number (unless the caller would convert it... which it doesn't).
The path helper service is owned by us and not exposed as a public API. Hence, we can fully control the input.
I see that this is the case, but I am not a fan of the argument. For me it's a matter of separation of concerns. The concern of the wikiPageLinkMacro is to always return a proper URL for the given inputs. I don't care much whether it raises an error for invalid inputs or whether it escapes invalid input, but it should never output wrong URLs that came to life due to escaping errors.
On the argument of "we fully control the input": The question is who guarantees that there will never be a user input involved in any of the two other properties. The next feature we build might use this method again, but this time the provider ID does not come from a controlled regex, but from a select box. And maybe the value of that select box is persisted as a query parameter 🤷 That would pave the way to allow XSS with the capability of generating unintended URLs through this path helper.
To be honest, I want to turn around the question: Why should we care during the implementation of this method, in which ways callers may call it? Why would we add assumptions about callers never exposing one of the parameters, but sometimes exposing others?
Wouldn't it be simpler to treat all parameters equally and escape them all? It's not that there is a negative impact on escaping all parameters.
There was a problem hiding this comment.
Like said in the live session, I accepted the "plublicness" of the path helper service and escaped everything.
| } | ||
|
|
||
| public wikiPageLinkMacro(providerId:number, pageIdentifier:string, turboFrameId:string) { | ||
| return `${this.staticBase}/wiki_page_link_macro/load?provider_id=${providerId}&page_identifier=${encodeURIComponent(pageIdentifier)}&turbo_frame_id=${turboFrameId}`; |
There was a problem hiding this comment.
Similar to my comment above: This method here can't know that turboFrameId comes from a "trusted source". It's an external input that could contain anything. We should also go through encodeURIComponent to add it to the URL.
Ticket
#74710
What are you trying to accomplish?
What approach did you choose and why?
Related PR
opf/commonmark-ckeditor-build#115