|
1 |
| -private import javascript |
| 1 | +private import javascript |
2 | 2 | private import DataFlow
|
| 3 | +private import advanced_security.javascript.frameworks.ui5.JsonParser |
3 | 4 | private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
4 | 5 | private import advanced_security.javascript.frameworks.ui5.UI5View
|
| 6 | +private import advanced_security.javascript.frameworks.ui5.UI5HTML |
5 | 7 |
|
6 | 8 | module UI5 {
|
7 |
| - /** |
8 |
| - * Helper predicate checking if two elements are in the same Project |
9 |
| - */ |
10 |
| - predicate inSameUI5Project(File f1, File f2) { |
11 |
| - exists(Project p | p.isInThisProject(f1) and p.isInThisProject(f2)) |
| 9 | + private class ResourceRootPathString extends PathString { |
| 10 | + SapUiCoreScriptElement coreScript; |
| 11 | + |
| 12 | + ResourceRootPathString() { this = coreScript.getAResourceRoot().getRoot() } |
| 13 | + |
| 14 | + override Folder getARootFolder() { result = coreScript.getFile().getParentContainer() } |
12 | 15 | }
|
13 | 16 |
|
14 |
| - class Project extends Folder { |
15 |
| - /** |
16 |
| - * An UI5 project root folder. |
17 |
| - */ |
18 |
| - Project() { exists(File yamlFile | yamlFile = this.getFile("ui5.yaml")) } |
| 17 | + private newtype TResourceRoot = |
| 18 | + MkResourceRoot(string name, string root, string source) { |
| 19 | + exists( |
| 20 | + JsonParser<getAResourceRootConfig/0>::JsonObject config, |
| 21 | + JsonParser<getAResourceRootConfig/0>::JsonMember configEntry, SapUiCoreScriptElement coreScript |
| 22 | + | |
| 23 | + source = coreScript.getAttributeByName("data-sap-ui-resourceroots").getValue() and |
| 24 | + source = config.getSource() and |
| 25 | + config.getAMember() = configEntry |
| 26 | + | |
| 27 | + name = configEntry.getKey() and |
| 28 | + root = configEntry.getValue().asString() |
| 29 | + ) |
| 30 | + } |
| 31 | + |
| 32 | + class ResourceRoot extends TResourceRoot, MkResourceRoot { |
| 33 | + string getName() { this = MkResourceRoot(result, _, _) } |
| 34 | + |
| 35 | + string getRoot() { this = MkResourceRoot(_, result, _) } |
| 36 | + |
| 37 | + string getSource() { this = MkResourceRoot(_, _, result) } |
| 38 | + |
| 39 | + string toString() { result = this.getName() + ": " + this.getRoot() } |
| 40 | + } |
| 41 | + |
| 42 | + class ResolvedResourceRoot extends Container { |
| 43 | + ResourceRoot unresolvedRoot; |
| 44 | + ResolvedResourceRoot() { |
| 45 | + exists(ResourceRootPathString resourceRootPathString | unresolvedRoot.getRoot() = resourceRootPathString | |
| 46 | + this = resourceRootPathString.resolve(resourceRootPathString.getARootFolder()).getContainer()) |
| 47 | + } |
| 48 | + |
| 49 | + string getName() { |
| 50 | + result = unresolvedRoot.getName() |
| 51 | + } |
| 52 | + |
| 53 | + string getSource() { |
| 54 | + result = unresolvedRoot.getSource() |
| 55 | + } |
| 56 | + |
| 57 | + predicate contains(File file) { |
| 58 | + file.getParentContainer+() = this |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + private string getAResourceRootConfig() { |
| 63 | + result = any(SapUiCoreScriptElement script).getAttributeByName("data-sap-ui-resourceroots").getValue() |
| 64 | + } |
| 65 | + |
| 66 | + class SapUiCoreScriptElement extends HTML::ScriptElement { |
| 67 | + SapUiCoreScriptElement() { |
| 68 | + this.getSourcePath().matches(["%sap-ui-core.js", "%sap-ui-core-nojQuery.js"]) |
| 69 | + } |
| 70 | + |
| 71 | + ResourceRoot getAResourceRoot() { |
| 72 | + result.getSource() = this.getAttributeByName("data-sap-ui-resourceroots").getValue() |
| 73 | + } |
| 74 | + |
| 75 | + ResolvedResourceRoot getAResolvedResourceRoot() { |
| 76 | + result.getSource() = this.getAttributeByName("data-sap-ui-resourceroots").getValue() |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + /** A UI5 web application manifest associated with a bootstrapped UI5 web application. */ |
| 81 | + class WebAppManifest extends File { |
| 82 | + WebApp webapp; |
| 83 | + |
| 84 | + WebAppManifest() { |
| 85 | + this.getBaseName() = "manifest.json" and |
| 86 | + this.getParentContainer() = webapp.getWebAppFolder() |
| 87 | + } |
| 88 | + |
| 89 | + WebApp getWebapp() { result = webapp } |
| 90 | + } |
| 91 | + |
| 92 | + /** A UI5 bootstrapped web application. */ |
| 93 | + class WebApp extends HTML::HtmlFile { |
| 94 | + SapUiCoreScriptElement coreScript; |
| 95 | + |
| 96 | + WebApp() { coreScript.getFile() = this } |
| 97 | + |
| 98 | + File getAResource() { coreScript.getAResolvedResourceRoot().contains(result) } |
| 99 | + |
| 100 | + File getResource(string path) { |
| 101 | + getWebAppFolder().getAbsolutePath() + "/" + path = result.getAbsolutePath() |
| 102 | + } |
| 103 | + |
| 104 | + Folder getWebAppFolder() { result = this.getParentContainer() } |
| 105 | + |
| 106 | + WebAppManifest getManifest() { result.getWebapp() = this } |
19 | 107 |
|
20 | 108 | /**
|
21 |
| - * The `ui5.yaml` file that declares a UI5 application. |
| 109 | + * Gets the JavaScript module that serves as an entrypoint to this webapp. |
22 | 110 | */
|
23 |
| - File getProjectYaml() { result = this.getFile("ui5.yaml") } |
24 |
| - |
25 |
| - predicate isInThisProject(File file) { this = file.getParentContainer*() } |
| 111 | + File getInitialModule() { |
| 112 | + exists( |
| 113 | + string initialModuleResourcePath, string resolvedModulePath, |
| 114 | + ResolvedResourceRoot resourceRoot |
| 115 | + | |
| 116 | + initialModuleResourcePath = coreScript.getAttributeByName("data-sap-ui-onInit").getValue() and |
| 117 | + coreScript.getAResolvedResourceRoot() = resourceRoot and |
| 118 | + resolvedModulePath = |
| 119 | + initialModuleResourcePath |
| 120 | + .regexpReplaceAll("^module\\s*:\\s*", "") |
| 121 | + .replaceAll(resourceRoot.getName(), resourceRoot.getAbsolutePath()) and |
| 122 | + result.getAbsolutePath() = resolvedModulePath + ".js" |
| 123 | + ) |
| 124 | + } |
26 | 125 |
|
27 |
| - private HTML::HtmlFile getSapUICoreScript() { |
28 |
| - exists(HTML::ScriptElement script | |
29 |
| - result = script.getFile() and |
30 |
| - this.isInThisProject(result) and |
31 |
| - script.getSourcePath().matches("%/sap-ui-core.js") |
| 126 | + FrameOptions getFrameOptions() { |
| 127 | + exists(HTML::DocumentElement doc | doc.getFile() = this | |
| 128 | + result.asHtmlFrameOptions() = coreScript.getAnAttribute() |
32 | 129 | )
|
| 130 | + or |
| 131 | + result.asJsFrameOptions().getFile() = this |
33 | 132 | }
|
34 | 133 |
|
35 |
| - HTML::HtmlFile getMainHTML() { result = this.getSapUICoreScript() } |
| 134 | + HTML::DocumentElement getDocument() { |
| 135 | + result.getFile() = this |
| 136 | + } |
36 | 137 | }
|
37 | 138 |
|
38 | 139 | /**
|
@@ -76,7 +177,7 @@ module UI5 {
|
76 | 177 | )
|
77 | 178 | }
|
78 | 179 |
|
79 |
| - Project getProject() { result = this.getFile().getParentContainer*() } |
| 180 | + WebApp getWebApp() { this.getFile() = result.getAResource() } |
80 | 181 |
|
81 | 182 | SapDefineModule getExtendingDefine() {
|
82 | 183 | exists(Extension baseExtension, Extension subclassExtension, SapDefineModule subclassDefine |
|
@@ -291,13 +392,7 @@ module UI5 {
|
291 | 392 | */
|
292 | 393 | bindingset[path]
|
293 | 394 | JsonObject resolveDirectPath(string path) {
|
294 |
| - exists(Project project, File jsonFile | |
295 |
| - // project contains this file |
296 |
| - project.isInThisProject(jsonFile) and |
297 |
| - jsonFile.getExtension() = "json" and |
298 |
| - jsonFile.getAbsolutePath() = project.getASubFolder().getAbsolutePath() + "/" + path and |
299 |
| - result.getJsonFile() = jsonFile |
300 |
| - ) |
| 395 | + exists(WebApp webApp | result.getJsonFile() = webApp.getResource(path)) |
301 | 396 | }
|
302 | 397 |
|
303 | 398 | /**
|
@@ -502,14 +597,18 @@ module UI5 {
|
502 | 597 | result.getMethodName() = "setProperty" and
|
503 | 598 | result.getArgument(0).asExpr().(StringLiteral).getValue() = propName and
|
504 | 599 | // TODO: in same controller
|
505 |
| - inSameUI5Project(this.getFile(), result.getFile()) |
| 600 | + exists(WebApp webApp | |
| 601 | + webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile() |
| 602 | + ) |
506 | 603 | }
|
507 | 604 |
|
508 | 605 | bindingset[propName]
|
509 | 606 | MethodCallNode getARead(string propName) {
|
510 | 607 | result.getMethodName() = "get" + capitalize(propName) and
|
511 | 608 | // TODO: in same controller
|
512 |
| - inSameUI5Project(this.getFile(), result.getFile()) |
| 609 | + exists(WebApp webApp | |
| 610 | + webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile() |
| 611 | + ) |
513 | 612 | }
|
514 | 613 | }
|
515 | 614 | }
|
0 commit comments