Skip to content

Commit

Permalink
Merge branch 'main' into jeongsoolee09/setModel-unit-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jeongsoolee09 authored Nov 28, 2023
2 parents aaaeb88 + 36c0485 commit 0a9cda4
Show file tree
Hide file tree
Showing 9 changed files with 828 additions and 80 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,38 +1,139 @@
private import javascript
private import javascript
private import DataFlow
private import advanced_security.javascript.frameworks.ui5.JsonParser
private import semmle.javascript.security.dataflow.DomBasedXssCustomizations
private import advanced_security.javascript.frameworks.ui5.UI5View
private import advanced_security.javascript.frameworks.ui5.UI5HTML

module UI5 {
/**
* Helper predicate checking if two elements are in the same Project
*/
predicate inSameUI5Project(File f1, File f2) {
exists(Project p | p.isInThisProject(f1) and p.isInThisProject(f2))
private class ResourceRootPathString extends PathString {
SapUiCoreScriptElement coreScript;

ResourceRootPathString() { this = coreScript.getAResourceRoot().getRoot() }

override Folder getARootFolder() { result = coreScript.getFile().getParentContainer() }
}

class Project extends Folder {
/**
* An UI5 project root folder.
*/
Project() { exists(File yamlFile | yamlFile = this.getFile("ui5.yaml")) }
private newtype TResourceRoot =
MkResourceRoot(string name, string root, string source) {
exists(
JsonParser<getAResourceRootConfig/0>::JsonObject config,
JsonParser<getAResourceRootConfig/0>::JsonMember configEntry, SapUiCoreScriptElement coreScript
|
source = coreScript.getAttributeByName("data-sap-ui-resourceroots").getValue() and
source = config.getSource() and
config.getAMember() = configEntry
|
name = configEntry.getKey() and
root = configEntry.getValue().asString()
)
}

class ResourceRoot extends TResourceRoot, MkResourceRoot {
string getName() { this = MkResourceRoot(result, _, _) }

string getRoot() { this = MkResourceRoot(_, result, _) }

string getSource() { this = MkResourceRoot(_, _, result) }

string toString() { result = this.getName() + ": " + this.getRoot() }
}

class ResolvedResourceRoot extends Container {
ResourceRoot unresolvedRoot;
ResolvedResourceRoot() {
exists(ResourceRootPathString resourceRootPathString | unresolvedRoot.getRoot() = resourceRootPathString |
this = resourceRootPathString.resolve(resourceRootPathString.getARootFolder()).getContainer())
}

string getName() {
result = unresolvedRoot.getName()
}

string getSource() {
result = unresolvedRoot.getSource()
}

predicate contains(File file) {
file.getParentContainer+() = this
}
}

private string getAResourceRootConfig() {
result = any(SapUiCoreScriptElement script).getAttributeByName("data-sap-ui-resourceroots").getValue()
}

class SapUiCoreScriptElement extends HTML::ScriptElement {
SapUiCoreScriptElement() {
this.getSourcePath().matches(["%sap-ui-core.js", "%sap-ui-core-nojQuery.js"])
}

ResourceRoot getAResourceRoot() {
result.getSource() = this.getAttributeByName("data-sap-ui-resourceroots").getValue()
}

ResolvedResourceRoot getAResolvedResourceRoot() {
result.getSource() = this.getAttributeByName("data-sap-ui-resourceroots").getValue()
}
}

/** A UI5 web application manifest associated with a bootstrapped UI5 web application. */
class WebAppManifest extends File {
WebApp webapp;

WebAppManifest() {
this.getBaseName() = "manifest.json" and
this.getParentContainer() = webapp.getWebAppFolder()
}

WebApp getWebapp() { result = webapp }
}

/** A UI5 bootstrapped web application. */
class WebApp extends HTML::HtmlFile {
SapUiCoreScriptElement coreScript;

WebApp() { coreScript.getFile() = this }

File getAResource() { coreScript.getAResolvedResourceRoot().contains(result) }

File getResource(string path) {
getWebAppFolder().getAbsolutePath() + "/" + path = result.getAbsolutePath()
}

Folder getWebAppFolder() { result = this.getParentContainer() }

WebAppManifest getManifest() { result.getWebapp() = this }

/**
* The `ui5.yaml` file that declares a UI5 application.
* Gets the JavaScript module that serves as an entrypoint to this webapp.
*/
File getProjectYaml() { result = this.getFile("ui5.yaml") }

predicate isInThisProject(File file) { this = file.getParentContainer*() }
File getInitialModule() {
exists(
string initialModuleResourcePath, string resolvedModulePath,
ResolvedResourceRoot resourceRoot
|
initialModuleResourcePath = coreScript.getAttributeByName("data-sap-ui-onInit").getValue() and
coreScript.getAResolvedResourceRoot() = resourceRoot and
resolvedModulePath =
initialModuleResourcePath
.regexpReplaceAll("^module\\s*:\\s*", "")
.replaceAll(resourceRoot.getName(), resourceRoot.getAbsolutePath()) and
result.getAbsolutePath() = resolvedModulePath + ".js"
)
}

private HTML::HtmlFile getSapUICoreScript() {
exists(HTML::ScriptElement script |
result = script.getFile() and
this.isInThisProject(result) and
script.getSourcePath().matches("%/sap-ui-core.js")
FrameOptions getFrameOptions() {
exists(HTML::DocumentElement doc | doc.getFile() = this |
result.asHtmlFrameOptions() = coreScript.getAnAttribute()
)
or
result.asJsFrameOptions().getFile() = this
}

HTML::HtmlFile getMainHTML() { result = this.getSapUICoreScript() }
HTML::DocumentElement getDocument() {
result.getFile() = this
}
}

/**
Expand Down Expand Up @@ -76,7 +177,7 @@ module UI5 {
)
}

Project getProject() { result = this.getFile().getParentContainer*() }
WebApp getWebApp() { this.getFile() = result.getAResource() }

SapDefineModule getExtendingDefine() {
exists(Extension baseExtension, Extension subclassExtension, SapDefineModule subclassDefine |
Expand Down Expand Up @@ -291,13 +392,7 @@ module UI5 {
*/
bindingset[path]
JsonObject resolveDirectPath(string path) {
exists(Project project, File jsonFile |
// project contains this file
project.isInThisProject(jsonFile) and
jsonFile.getExtension() = "json" and
jsonFile.getAbsolutePath() = project.getASubFolder().getAbsolutePath() + "/" + path and
result.getJsonFile() = jsonFile
)
exists(WebApp webApp | result.getJsonFile() = webApp.getResource(path))
}

/**
Expand Down Expand Up @@ -502,14 +597,18 @@ module UI5 {
result.getMethodName() = "setProperty" and
result.getArgument(0).asExpr().(StringLiteral).getValue() = propName and
// TODO: in same controller
inSameUI5Project(this.getFile(), result.getFile())
exists(WebApp webApp |
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
)
}

bindingset[propName]
MethodCallNode getARead(string propName) {
result.getMethodName() = "get" + capitalize(propName) and
// TODO: in same controller
inSameUI5Project(this.getFile(), result.getFile())
exists(WebApp webApp |
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ module UI5DataFlow {
private predicate bidiModelControl(DataFlow::Node start, DataFlow::Node end) {
exists(DataFlow::SourceNode property, Metadata metadata, UI5BoundNode node |
// same project
inSameUI5Project(metadata.getFile(), node.getFile()) and
exists(WebApp webApp |
webApp.getAResource() = metadata.getFile() and webApp.getAResource() = node.getFile()
) and
(
// same control
metadata.getControl().getName() = node.getBindingPath().getControlQualifiedType()
Expand Down Expand Up @@ -87,21 +89,24 @@ module UI5DataFlow {
UI5BindingPath getBindingPath() { result = bindingPath }

UI5BoundNode() {
/* The relevant portion of the content of a JSONModel */
exists(Property p, JsonModel model |
// The property bound to an UI5View source
this.(DataFlow::PropRef).getPropertyNameExpr() = p.getNameExpr() and
// The binding path refers to this model
bindingPath.getAbsolutePath() = model.getPathString(p) and
inSameUI5Project(this.getFile(), bindingPath.getFile())
)
or
/* The URI string to the JSONModel constructor call */
exists(JsonModel model |
this = model.getArgument(0) and
this.asExpr() instanceof StringLiteral and
bindingPath.getAbsolutePath() = model.getPathString() and
inSameUI5Project(this.getFile(), bindingPath.getFile())
exists(WebApp webApp |
webApp.getAResource() = this.getFile() and
webApp.getAResource() = bindingPath.getFile()
|
/* The relevant portion of the content of a JSONModel */
exists(Property p, JsonModel model |
// The property bound to an UI5View source
this.(DataFlow::PropRef).getPropertyNameExpr() = p.getNameExpr() and
// The binding path refers to this model
bindingPath.getAbsolutePath() = model.getPathString(p)
)
or
/* The URI string to the JSONModel constructor call */
exists(JsonModel model |
this = model.getArgument(0) and
this.asExpr() instanceof StringLiteral and
bindingPath.getAbsolutePath() = model.getPathString()
)
)
}
}
Expand All @@ -112,9 +117,7 @@ module UI5DataFlow {
class UI5ModelSource extends UI5DataFlow::UI5BoundNode, RemoteFlowSource {
UI5ModelSource() { bindingPath = any(UI5View view).getASource() }

override string getSourceType() {
result = "UI5 model remote flow source"
}
override string getSourceType() { result = "UI5 model remote flow source" }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ class FrameOptions extends TFrameOptions {
}

/**
* Holds if the frame options are left untouched as the default value `trusted`.
* Holds if there are no frame options specified to prevent click jacking.
*/
predicate thereIsNoFrameOptionSet(UI5::Project p) {
not exists(FrameOptions frameOptions | p.isInThisProject(frameOptions.getLocation().getFile()) |
predicate isMissingFrameOptionsToPreventClickjacking(UI5::WebApp webapp) {
not exists(FrameOptions frameOptions | webapp.getFrameOptions() = frameOptions |
frameOptions.allowsSharedOriginEmbedding() or
frameOptions.deniesEmbedding() or
frameOptions.allowsAllOriginEmbedding()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ abstract class UI5BindingPath extends Locatable {
// The property bound to an UI5View source
result.getPropertyNameExpr() = p.getNameExpr() and
this.getAbsolutePath() = model.getPathString(p) and
//restrict search inside the same project
inSameUI5Project(this.getFile(), result.getFile())
//restrict search inside the same webapp
exists(WebApp webApp |
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
)
)
// TODO
/*
Expand Down Expand Up @@ -142,8 +144,10 @@ abstract class UI5View extends File {
CustomController getController() {
// The controller name should match
result.getName() = this.getControllerName() and
// The View and the Controller are in a same project
inSameUI5Project(this, result.getFile())
// The View and the Controller are in a same webapp
exists(WebApp webApp |
webApp.getAResource() = this and webApp.getAResource() = result.getFile()
)
}

abstract UI5BindingPath getASource();
Expand Down Expand Up @@ -490,10 +494,13 @@ class XmlView extends UI5View, XmlFile {
(
builtInControl(element.getNamespace())
or
// or a custom control with implementation code found in the project
// or a custom control with implementation code found in the webapp
exists(CustomControl control |
control.getName() = element.getNamespace().getUri() + "." + element.getName() and
inSameUI5Project(control.getFile(), element.getFile())
exists(WebApp webApp |
webApp.getAResource() = control.getFile() and
webApp.getAResource() = element.getFile()
)
)
)
)
Expand Down Expand Up @@ -563,20 +570,26 @@ class XmlControl extends UI5Control instanceof XmlElement {

override CustomControl getDefinition() {
result.getName() = this.getQualifiedType() and
inSameUI5Project(this.getFile(), result.getFile())
exists(WebApp webApp |
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
)
}

bindingset[propName]
override MethodCallNode getARead(string propName) {
// TODO: in same view
inSameUI5Project(this.getFile(), result.getFile()) and
exists(WebApp webApp |
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
) and
result.getMethodName() = "get" + capitalize(propName)
}

bindingset[propName]
override MethodCallNode getAWrite(string propName) {
// TODO: in same view
inSameUI5Project(this.getFile(), result.getFile()) and
exists(WebApp webApp |
webApp.getAResource() = this.getFile() and webApp.getAResource() = result.getFile()
) and
result.getMethodName() = "set" + capitalize(propName)
}

Expand Down
Loading

0 comments on commit 0a9cda4

Please sign in to comment.