cba2b9ba by acetousk

Secure grapesJs services, improve their logic, add notes

1 parent 600190dc
...@@ -171,7 +171,7 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -171,7 +171,7 @@ along with this software (see the LICENSE.md file). If not, see
171 window.emailTemplateId = new URLSearchParams(window.location.search).get('emailTemplateId'); 171 window.emailTemplateId = new URLSearchParams(window.location.search).get('emailTemplateId');
172 172
173 const request = new XMLHttpRequest(); 173 const request = new XMLHttpRequest();
174 request.open("GET", ("${baseLinkUrl}/rest/s1/moqui-mjml/mjml?grapesLocation="+window.grapesLocation+"&htmlLocation="+window.htmlLocation+"&emailTemplateId="+window.emailTemplateId), false); // `false` makes the request synchronous 174 request.open("GET", ("${baseLinkUrl}/rest/s1/moqui-mjml/mjml?emailTemplateId="+window.emailTemplateId), false); // `false` makes the request synchronous
175 request.send(null); 175 request.send(null);
176 176
177 let response; 177 let response;
...@@ -203,8 +203,8 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -203,8 +203,8 @@ along with this software (see the LICENSE.md file). If not, see
203 // Default storage options 203 // Default storage options
204 options: { 204 options: {
205 remote: { 205 remote: {
206 urlLoad: "${baseLinkUrl}/rest/s1/moqui-mjml/mjml?grapesLocation="+window.grapesLocation+"&htmlLocation="+window.htmlLocation, 206 urlLoad: "${baseLinkUrl}/rest/s1/moqui-mjml/mjml?emailTemplateId="+window.emailTemplateId,
207 urlStore: "${baseLinkUrl}/rest/s1/moqui-mjml/mjml?grapesLocation="+window.grapesLocation+"&htmlLocation="+window.htmlLocation, 207 urlStore: "${baseLinkUrl}/rest/s1/moqui-mjml/mjml?emailTemplateId="+window.emailTemplateId,
208 headers: { 208 headers: {
209 "X-CSRF-Token": document.getElementById('confMoquiSessionToken').value 209 "X-CSRF-Token": document.getElementById('confMoquiSessionToken').value
210 }, 210 },
...@@ -222,9 +222,11 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -222,9 +222,11 @@ along with this software (see the LICENSE.md file). If not, see
222 const url = new URL(window.location.href) 222 const url = new URL(window.location.href)
223 url.searchParams.set('grapesLocation', result.grapesLocation); 223 url.searchParams.set('grapesLocation', result.grapesLocation);
224 url.searchParams.set('htmlLocation', result.htmlLocation); 224 url.searchParams.set('htmlLocation', result.htmlLocation);
225 url.searchParams.set('emailTemplateId', result.emailTemplateId);
225 window.history.pushState({}, '', url) 226 window.history.pushState({}, '', url)
226 window.grapesLocation = result.grapesLocation; 227 window.grapesLocation = result.grapesLocation;
227 window.htmlLocation = result.htmlLocation; 228 window.htmlLocation = result.htmlLocation;
229 window.emailTemplateId = result.emailTemplateId;
228 } 230 }
229 // console.log('onLoad ', result) 231 // console.log('onLoad ', result)
230 return result.data 232 return result.data
......
...@@ -15,11 +15,11 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -15,11 +15,11 @@ along with this software (see the LICENSE.md file). If not, see
15 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd"> 15 <services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-definition-3.xsd">
16 16
17 <service verb="load" noun="GrapeJs"> 17 <service verb="load" noun="GrapeJs">
18 <description>Create MJML</description> 18 <description>Load GrapesJs resource. Can be adapted to entity other than EmailTemplate, but must have data for the grapesLocation and htmlLocation to ensure safety of read and write of resources.</description>
19 <in-parameters> 19 <in-parameters>
20 <parameter name="grapesLocation"/> 20 <parameter name="grapesLocation"/>
21 <parameter name="htmlLocation"/> 21 <parameter name="htmlLocation"/>
22 <parameter name="emailTemplateId"/> 22 <parameter name="emailTemplateId" required="true"/>
23 </in-parameters> 23 </in-parameters>
24 <out-parameters> 24 <out-parameters>
25 <parameter name="grapesLocation"/> 25 <parameter name="grapesLocation"/>
...@@ -31,21 +31,10 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -31,21 +31,10 @@ along with this software (see the LICENSE.md file). If not, see
31 <if condition="htmlLocation == 'null'"><set field="htmlLocation" from="null"/></if> 31 <if condition="htmlLocation == 'null'"><set field="htmlLocation" from="null"/></if>
32 <!-- <log level="warn" message="resourceId is ${resourceId} resourceId.getClass().getName() ${resourceId.getClass().getName()} resourceId == 'null' ${resourceId == 'null'} resourceId == null ${resourceId == null}"/>--> 32 <!-- <log level="warn" message="resourceId is ${resourceId} resourceId.getClass().getName() ${resourceId.getClass().getName()} resourceId == 'null' ${resourceId == 'null'} resourceId == null ${resourceId == null}"/>-->
33 <!-- <log level="warn" message="load context.toString() ${context.toString()}"/>--> 33 <!-- <log level="warn" message="load context.toString() ${context.toString()}"/>-->
34 <if condition="emailTemplateId"> 34 <entity-find-one entity-name="moqui.basic.email.EmailTemplate" value-field="emailTemplate" auto-field-map="[emailTemplateId:emailTemplateId]"/>
35 <entity-find-one entity-name="moqui.basic.email.EmailTemplate" value-field="emailTemplate" auto-field-map="[emailTemplateId:emailTemplateId]"/> 35 <if condition="!emailTemplate"><return error="true" message="Resource not found"/></if>
36 <if condition="!grapesLocation"><set field="grapesLocation" from="emailTemplate?.grapesLocation"/></if> 36 <set field="grapesLocation" from="emailTemplate?.grapesLocation"/>
37 <if condition="grapesLocation &amp;&amp; grapesLocation != emailTemplate?.grapesLocation"> 37 <set field="htmlLocation" from="emailTemplate?.htmlLocation"/>
38 <!-- This should almost never happen, but if it does it basically creates a resource leak for the previously used resource -->
39 <log message="Changing resource id from ${grapesLocation} to ${emailTemplate?.grapesLocation} for email template ${emailTemplateId}"/>
40 <service-call name="update#moqui.basic.email.EmailTemplate" in-map="[emailTemplateId:emailTemplateId,grapesLocation:grapesLocation]"/>
41 </if>
42 <if condition="!htmlLocation"><set field="htmlLocation" from="emailTemplate?.htmlLocation"/></if>
43 <if condition="htmlLocation &amp;&amp; htmlLocation != emailTemplate?.htmlLocation">
44 <!-- This should almost never happen, but if it does it basically creates a resource leak for the previously used resource -->
45 <log message="Changing resource id from ${htmlLocation} to ${emailTemplate?.htmlLocation} for email template ${emailTemplateId}"/>
46 <service-call name="update#moqui.basic.email.EmailTemplate" in-map="[emailTemplateId:emailTemplateId,htmlLocation:htmlLocation]"/>
47 </if>
48 </if>
49 38
50 <set field="grapesJsResource" from="ec.resource.getLocationReference('dbresource://grapesjs/project')"/> 39 <set field="grapesJsResource" from="ec.resource.getLocationReference('dbresource://grapesjs/project')"/>
51 <if condition="!grapesLocation &amp;&amp; !htmlLocation"> 40 <if condition="!grapesLocation &amp;&amp; !htmlLocation">
...@@ -62,11 +51,9 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -62,11 +51,9 @@ along with this software (see the LICENSE.md file). If not, see
62 grapesFile.move(grapesLocation) 51 grapesFile.move(grapesLocation)
63 ]]></script> 52 ]]></script>
64 53
65 <if condition="emailTemplateId"> 54 <service-call name="update#moqui.basic.email.EmailTemplate" in-map="[emailTemplateId:emailTemplateId,grapesLocation:grapesLocation,htmlLocation:htmlLocation]"/>
66 <service-call name="update#moqui.basic.email.EmailTemplate" in-map="[emailTemplateId:emailTemplateId,grapesLocation:grapesLocation,htmlLocation:htmlLocation]"/>
67 </if>
68 </then> 55 </then>
69 <else> 56 <else-if condition="grapesLocation &amp;&amp; htmlLocation">
70 <set field="putDbResource" from="ec.resource.getLocationReference(grapesLocation)"/> 57 <set field="putDbResource" from="ec.resource.getLocationReference(grapesLocation)"/>
71 <!-- TODO: Is this a strong enough check to prevent unauthorized access? --> 58 <!-- TODO: Is this a strong enough check to prevent unauthorized access? -->
72 <if condition="!putDbResource || putDbResource.parent?.location != grapesJsResource.location"> 59 <if condition="!putDbResource || putDbResource.parent?.location != grapesJsResource.location">
...@@ -74,65 +61,90 @@ along with this software (see the LICENSE.md file). If not, see ...@@ -74,65 +61,90 @@ along with this software (see the LICENSE.md file). If not, see
74 </if> 61 </if>
75 62
76 <set field="data" from="putDbResource.getText()"/> 63 <set field="data" from="putDbResource.getText()"/>
64 </else-if>
65 <else>
66 <return error="true" message="Resource not found"/>
77 </else> 67 </else>
78 </if> 68 </if>
79 </actions> 69 </actions>
80 </service> 70 </service>
81 <service verb="store" noun="GrapeJs" authenticate="anonymous-all"> 71 <service verb="store" noun="GrapeJs" authenticate="anonymous-all">
82 <description>Create MJML</description> 72 <description>Store GrapesJs resource. Can be adapted to entity other than EmailTemplate, but must have data for the grapesLocation and htmlLocation to ensure safety of read and write of resources.</description>
83 <in-parameters> 73 <in-parameters>
84 <parameter name="resourceId"/> 74 <parameter name="htmlLocation"/>
75 <parameter name="grapesLocation"/>
76 <parameter name="emailTemplateId"/>
85 <parameter name="data"/> 77 <parameter name="data"/>
86 <parameter name="html" allow-html="any"/> 78 <parameter name="html" allow-html="any"/>
87 </in-parameters> 79 </in-parameters>
88 <out-parameters> 80 <out-parameters>
89 <parameter name="resourceId"/> 81 <parameter name="htmlLocation"/>
82 <parameter name="grapesLocation"/>
90 </out-parameters> 83 </out-parameters>
91 <actions> 84 <actions>
92 <if condition="resourceId == 'null'"><set field="resourceId" from="null"/></if> 85 <if condition="htmlLocation == 'null'"><set field="htmlLocation" from="null"/></if>
86 <if condition="grapesLocation == 'null'"><set field="grapesLocation" from="null"/></if>
87 <entity-find-one entity-name="moqui.basic.email.EmailTemplate" value-field="emailTemplate" auto-field-map="[emailTemplateId:emailTemplateId]"/>
88 <if condition="!emailTemplate"><return error="true" message="Resource not found"/></if>
89 <set field="grapesLocation" from="emailTemplate?.grapesLocation"/>
90 <set field="htmlLocation" from="emailTemplate?.htmlLocation"/>
93 <set field="data" from="groovy.json.JsonOutput.toJson(new groovy.json.JsonSlurper().parseText(ec.web.secureRequestParameters._requestBodyText).data)"/> 91 <set field="data" from="groovy.json.JsonOutput.toJson(new groovy.json.JsonSlurper().parseText(ec.web.secureRequestParameters._requestBodyText).data)"/>
94 92
95 <if condition="!resourceId"> 93 <set field="grapesJsResource" from="ec.resource.getLocationReference('dbresource://grapesjs/project')"/>
94 <if condition="!htmlLocation &amp;&amp; !grapesLocation">
96 <then> 95 <then>
97 <set field="grapesJsResource" from="ec.resource.getLocationReference('dbresource://grapesjs/project')"/> 96 <!-- TODO: This should work, but isn't used anywhere and is untested.
98 <service-call name="create#moqui.resource.DbResource" in-map="[parentResourceId:grapesJsResource.getDbResourceId(),isFile:'Y']" out-map="dbResource"/> 97 <set field="htmlFile" from="grapesJsResource.makeFile(java.util.UUID.randomUUID().toString()+'.html')"/>
99 <service-call name="update#moqui.resource.DbResource" in-map="[resourceId:dbResource.resourceId,filename:dbResource.resourceId+'.json']" out-map="dbResource"/> 98 <set field="grapesFile" from="grapesJsResource.makeFile(java.util.UUID.randomUUID().toString()+'.json')"/>
100 <set field="versionName" value="01"/>
101 <service-call name="create#moqui.resource.DbResourceFile" in-map="[resourceId: dbResource.resourceId,mimeType: 'text/json',versionName: versionName,rootVersionName: versionName,fileData:data]"/>
102 <service-call name="create#moqui.resource.DbResourceFileHistory" in-map="[resourceId: dbResource.resourceId,versionDate: ec.user.nowTimestamp,userId: ec.user.userId,isDiff: 'N']"/>
103 <set field="resourceId" from="dbResource.resourceId"/>
104 99
105 <set field="htmlFile" from="grapesJsResource.makeFile(resourceId.toString()+'.html')"/> 100 <set field="htmlLocation" from="grapesJsResource.location + '/' + htmlFile?.dbResourceId + '.html'"/>
106 <script><![CDATA[htmlFile.putText(html)]]></script> 101 <set field="grapesLocation" from="grapesJsResource.location + '/' + grapesFile?.dbResourceId + '.json'"/>
102 <if condition="ec.resource.getLocationReference(htmlLocation).parent?.location != grapesJsResource.location">
103 <return error="true" message="Resource not found"/>
104 </if>
105 <if condition="ec.resource.getLocationReference(grapesLocation).parent?.location != grapesJsResource.location">
106 <return error="true" message="Resource not found"/>
107 </if>
108 <script><![CDATA[
109 htmlFile.putText(html)
110 htmlFile.move(htmlLocation)
111 grapesFile.putText(data)
112 grapesFile.move(grapesLocation)
113 ]]></script>-->
114 <return error="true" message="Resource not found"/>
107 </then> 115 </then>
108 <else> 116 <else-if condition="grapesLocation">
109 <entity-find-one entity-name="moqui.resource.DbResource" value-field="dbResource" auto-field-map="[resourceId:resourceId]"/> 117 <set field="grapesFile" from="ec.resource.getLocationReference(grapesLocation)"/>
110 <if condition="!dbResource"> 118 <if condition="!grapesFile || grapesFile.parent?.location != grapesJsResource.location">
111 <return error="true" message="Resource not found"/> 119 <return error="true" message="Resource not found"/>
112 </if> 120 </if>
113 <set field="actualDbResourcePath" from="null"/> 121 <script>grapesFile.putText(data)</script>
114 <set field="dbResourcePath" from="dbResource.filename"/>
115 <set field="lastDbResource" from="dbResource"/>
116 <script>
117 while (actualDbResourcePath == null) {
118 if (lastDbResource.parentResourceId == null) {
119 dbResourcePath = 'dbresource://'+dbResourcePath
120 actualDbResourcePath = dbResourcePath
121 } else {
122 lastDbResource = ec.entity.fastFindOne("moqui.resource.DbResource", true, false, lastDbResource.parentResourceId)
123 dbResourcePath = lastDbResource.filename+'/'+dbResourcePath
124 }
125 }
126 </script>
127 <set field="putDbResource" from="ec.resource.getLocationReference(actualDbResourcePath)"/>
128 <script>
129 putDbResource.putText(data)
130 </script>
131 122
132 <set field="grapesJsResource" from="ec.resource.getLocationReference('dbresource://grapesjs/project')"/> 123 <set field="htmlFile" from="null"/>
133 <set field="htmlFile" from="grapesJsResource.findChildFile(resourceId.toString()+'.html') ?: grapesJsResource.makeFile(resourceId.toString()+'.html')"/> 124 <if condition="htmlLocation"><then>
134 <script><![CDATA[htmlFile.putText(html)]]></script> 125 <set field="htmlFile" from="ec.resource.getLocationReference(htmlLocation)"/>
126 </then><else>
127 <entity-find entity-name="moqui.basic.email.EmailTemplate" list="emailTemplateList">
128 <econdition field-name="grapesLocation"/>
129 <order-by field-name="-lastUpdatedStamp"/></entity-find>
130 <set field="emailTemplate" from="emailTemplateList.getFirst()"/>
131 <if condition="emailTemplateList.size() == 1 &amp;&amp; emailTemplate.htmlLocation"><then>
132 <set field="htmlFile" from="ec.resource.getLocationReference(emailTemplate.htmlLocation)"/>
133 </then><else>
134 <set field="htmlFile" from="grapesJsResource.makeFile(java.util.UUID.randomUUID().toString()+'.html')"/>
135 <set field="htmlLocation" from="grapesJsResource.location + '/' + htmlFile?.dbResourceId + '.html'"/>
136 <script><![CDATA[htmlFile.move(htmlLocation)]]></script>
  • This doesn't look right. htmlFile is created with a random name, no content, and then immediately moved to the dbResourceId based name. This should then mean htmlFile itself is invalid(I just read the source code of the ContentResourceReference:move() method). Further down, the rest of this service then accesses htmlFile.

135 137
138 <service-call name="update#moqui.basic.email.EmailTemplate" in-map="[emailTemplateId:emailTemplateId,htmlLocation:htmlLocation]"/>
139 </else></if>
140 </else></if>
141 <if condition="!htmlFile || htmlFile.parent?.location != grapesJsResource.location">
142 <return error="true" message="Resource not found"/>
143 </if>
144 <script><![CDATA[htmlFile.putText(html)]]></script>
145 </else-if>
146 <else>
147 <return error="true" message="Resource not found"/>
136 </else> 148 </else>
137 </if> 149 </if>
138 </actions> 150 </actions>
......