From e1d81e28e158e863c485b967bf82ecca364309b0 Mon Sep 17 00:00:00 2001 From: andreas Date: Sat, 25 Feb 2023 11:02:21 +0100 Subject: [PATCH 01/39] feat: added support for recognition of database query types --- .../graph/passes/DatabaseOperationPass.kt | 6 +++-- .../graph/passes/GormDatabasePass.kt | 9 ++++--- .../graph/passes/python/Psycopg2Pass.kt | 3 ++- .../graph/passes/python/PyMongoPass.kt | 25 +++++++++++++++++-- .../graph/utils/DatabaseQueryType.kt | 5 ++++ 5 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt index ee08ad1..d7fe00a 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt @@ -7,6 +7,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.passes.Pass import io.clouditor.graph.* +import io.clouditor.graph.utils.DatabaseQueryType abstract class DatabaseOperationPass : Pass() { @@ -41,9 +42,10 @@ abstract class DatabaseOperationPass : Pass() { connect: DatabaseConnect, storage: List, calls: List, - app: Application? + app: Application?, + type: DatabaseQueryType ): DatabaseQuery { - val op = DatabaseQuery(modify, calls, storage, connect.to) + val op = DatabaseQuery(modify, calls, storage, connect.to, type) op.location = app?.location storage.forEach { diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt index c957832..4792c06 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt @@ -13,6 +13,7 @@ import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import io.clouditor.graph.* import io.clouditor.graph.nodes.getStorageOrCreate +import io.clouditor.graph.utils.DatabaseQueryType class GormDatabasePass : DatabaseOperationPass() { override fun accept(t: TranslationResult) { @@ -116,7 +117,7 @@ class GormDatabasePass : DatabaseOperationPass() { val op = app?.functionalities?.filterIsInstance()?.firstOrNull()?.let { - val op = createDatabaseQuery(result, false, it, mutableListOf(), calls, app) + val op = createDatabaseQuery(result, false, it, mutableListOf(), calls, app, DatabaseQueryType.READ) op.name = call.name // loop through the calls and set DFG edges @@ -144,7 +145,8 @@ class GormDatabasePass : DatabaseOperationPass() { it, mutableListOf(), mutableListOf(call), - app + app, + DatabaseQueryType.CREATE ) op.name = call.name @@ -165,7 +167,8 @@ class GormDatabasePass : DatabaseOperationPass() { it, mutableListOf(), mutableListOf(call), - app + app, + DatabaseQueryType.UPDATE ) op.name = call.name diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt index 3b3efc9..58ff43d 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt @@ -9,6 +9,7 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import io.clouditor.graph.* import io.clouditor.graph.nodes.getStorageOrCreate import io.clouditor.graph.passes.DatabaseOperationPass +import io.clouditor.graph.utils.DatabaseQueryType class Psycopg2Pass : DatabaseOperationPass() { @@ -148,7 +149,7 @@ class Psycopg2Pass : DatabaseOperationPass() { val dbName = dbStorage.firstOrNull()?.name val storage = connect.to.map { it.getStorageOrCreate(table ?: "", dbName) } - val op = createDatabaseQuery(result, false, connect, storage, mutableListOf(call), app) + val op = createDatabaseQuery(result, false, connect, storage, mutableListOf(call), app, DatabaseQueryType.UNKNOWN) op.name = call.name // in the select case, the arguments are just arguments to the query itself and flow diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt index f35c7e1..0fac523 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt @@ -10,6 +10,7 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy import io.clouditor.graph.* import io.clouditor.graph.nodes.getStorageOrCreate import io.clouditor.graph.passes.DatabaseOperationPass +import io.clouditor.graph.utils.DatabaseQueryType import java.net.URI class PyMongoPass : DatabaseOperationPass() { @@ -168,14 +169,32 @@ class PyMongoPass : DatabaseOperationPass() { var (connect, storage) = pair var op: DatabaseQuery? = null if (mce.name == "insert_one") { - op = createDatabaseQuery(t, true, connect, storage, listOf(mce), app) + op = createDatabaseQuery(t, true, connect, storage, listOf(mce), app, DatabaseQueryType.CREATE) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) } if (mce.name == "find" || mce.name == "find_one") { - op = createDatabaseQuery(t, false, connect, storage, listOf(mce), app) + op = createDatabaseQuery(t, false, connect, storage, listOf(mce), app, DatabaseQueryType.READ) + // data flows from first argument to op + mce.arguments.firstOrNull()?.addNextDFG(op) + + // and towards the DFG target(s) of the call + mce.nextDFG.forEach { op!!.addNextDFG(it) } + } + + if (mce.name == "delete_one" || mce.name == "delete_many") { + op = createDatabaseQuery(t, true, connect, storage, listOf(mce), app, DatabaseQueryType.DELETE) + // data flows from first argument to op + mce.arguments.firstOrNull()?.addNextDFG(op) + + // and towards the DFG target(s) of the call + mce.nextDFG.forEach { op!!.addNextDFG(it) } + } + + if (mce.name == "update_one" || mce.name == "update_many") { + op = createDatabaseQuery(t, true, connect, storage, listOf(mce), app, DatabaseQueryType.UPDATE) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) @@ -186,6 +205,8 @@ class PyMongoPass : DatabaseOperationPass() { if (op != null) { op.name = mce.name } + + } override fun cleanup() { diff --git a/cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt b/cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt new file mode 100644 index 0000000..ed72aa7 --- /dev/null +++ b/cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt @@ -0,0 +1,5 @@ +package io.clouditor.graph.utils + +enum class DatabaseQueryType { + CREATE, READ, UPDATE, DELETE, UNKNOWN +} \ No newline at end of file From 4e5b295de1374ad6ccea4d9da2f2a7887c33816e Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 2 Mar 2023 22:28:36 +0100 Subject: [PATCH 02/39] feat: created testcases for compliance checks --- .../RightToDataPortability/Python/README.md | 1 + .../RightToDataPortability/Python/client.py | 21 ++++++++++++++ .../RightToDataPortability/Python/config.yml | 17 +++++++++++ .../RightToDataPortability/Python/server.py | 28 ++++++++++++++++++ .../RightToErasure/Python/README.md | 1 + .../RightToErasure/Python/client.py | 15 ++++++++++ .../RightToErasure/Python/config.yml | 17 +++++++++++ .../RightToErasure/Python/server.py | 29 +++++++++++++++++++ .../RightToRectification/Python/README.md | 1 + .../RightToRectification/Python/client.py | 17 +++++++++++ .../RightToRectification/Python/config.yml | 17 +++++++++++ .../RightToRectification/Python/server.py | 27 +++++++++++++++++ 12 files changed, 191 insertions(+) create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py new file mode 100755 index 0000000..8c953d5 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import requests +import os + +def get_own_data_in_machine_readable_format(): + url = 'test-online-notepad.com/data' + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "", + "notes": "" + } + # get the data from the server + personal_data = requests.get(url, json = personal_data) + f = open("personal_data.json", "w") + f.write(personal_data) + f.close() + +if __name__ == '__main__': + get_own_data_in_machine_readable_format() diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml new file mode 100644 index 0000000..cb48069 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml @@ -0,0 +1,17 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + directory: server + name: mongo + storages: + - userdata + - otherdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py new file mode 100755 index 0000000..3c3f21e --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['GET']) +def get_data_in_csv_format(): + req = request.json + data = { + "username": req['username'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['username']}), 200 + # send the data to the client + return user_data, 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py new file mode 100755 index 0000000..2a1578f --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import requests + +def delete_own_data(): + url = 'test-online-notepad.com/data' + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "notes": ["note1", "note2", "note3"] + } + requests.delete(url, json = personal_data) + +if __name__ == '__main__': + delete_own_data() diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml new file mode 100644 index 0000000..cb48069 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml @@ -0,0 +1,17 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + directory: server + name: mongo + storages: + - userdata + - otherdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py new file mode 100755 index 0000000..fa0d97f --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['DELETE']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + user_db_collection.delete_one({"username": data['username']}) + # inform external advertising server about the deletion + requests.delete("ext-ad-server.com/data", json = data) + return "Created", 201 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py new file mode 100755 index 0000000..b58a773 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import requests + +def rectify(changed_name): + url = 'test-online-notepad.com/data' + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "firstname lastname", + "notes": ["note1", "note2", "note3"] + } + personal_data["name"] = changed_name + requests.put(url, json = personal_data) + +if __name__ == '__main__': + rectify("New Name") diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml new file mode 100644 index 0000000..cb48069 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml @@ -0,0 +1,17 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + directory: server + name: mongo + storages: + - userdata + - otherdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py new file mode 100755 index 0000000..6dcfd3f --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "name": req['name'], + "notes": req['notes'] + } + if user_db_collection.find( { "name": data['name'] } ).count() > 0: + return "Conflict", 409 + else: + user_db_collection.update_one({"name": data['name']}) + return "Created", 201 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 43c1db5b06b6b0def1978a841d7a33250fa321a6 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 2 Mar 2023 22:30:06 +0100 Subject: [PATCH 03/39] feat: added support for detection of DELETE and PUT HttpRequest of python requests library --- .../java/io/clouditor/graph/passes/python/RequestsPass.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/RequestsPass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/RequestsPass.kt index c77540f..4a4bea3 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/python/RequestsPass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/RequestsPass.kt @@ -29,6 +29,10 @@ class RequestsPass : HttpClientPass() { handleClientRequest(tu, t, r, "GET") } else if (r.name == "post" && r.base.name == "requests") { handleClientRequest(tu, t, r, "POST") + } else if (r.name == "delete" && r.base.name == "requests") { + handleClientRequest(tu, t, r, "DELETE") + } else if (r.name == "put" && r.base.name == "requests") { + handleClientRequest(tu, t, r, "PUT") } } } From 2b922e77b75ce68cb8ade658c8f40b2184d28eec Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 2 Mar 2023 22:30:39 +0100 Subject: [PATCH 04/39] feat: created initial queries for compliance checks --- .../clouditor/graph/GDPRComplianceChecks.kt | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt new file mode 100644 index 0000000..2c10c66 --- /dev/null +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -0,0 +1,202 @@ +package io.clouditor.graph + +import io.clouditor.graph.utils.DatabaseQueryType +import kotlin.io.path.Path +import kotlin.test.assertEquals +import org.junit.Test +import org.junit.jupiter.api.Tag +import org.neo4j.driver.internal.InternalPath + +@Tag("TestingLibrary") +open class GDPRComplianceChecks { + + @Test + fun checkComplianceToArticle16() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" + + ), + listOf(Path(".")), + "MATCH p=(n:PseudoIdentifier)--()-[:DFG*]->(hr:HttpRequest)--()-[:DFG*]->(he:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'UPDATE') AND (hr.name = 'PUT') AND (he.method = 'PUT') WITH COLLECT(n) as pseudosWithUpdate MATCH path=(psi:PseudoIdentifier)--() WHERE NOT psi IN pseudosWithUpdate RETURN path" + ) + + // create a list for all pseudoidentifiers with no update call connected to them via a data flow + val listOfAllPseudoIdentifierWithNoUpdateByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoUpdateByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoUpdateByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 16, the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoUpdateByIdentity.size) + } + + @Test + fun checkComplianceToArticle17_paragraph_1() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" + + ), + listOf(Path(".")), + "MATCH p=(n:PseudoIdentifier)--()-[:DFG*]->(hr:HttpRequest)--()-[:DFG*]->(he:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'DELETE') AND (hr.name = 'DELETE') AND (he.method = 'DELETE') WITH COLLECT(n) as pseudosWithDelete MATCH path=(psi:PseudoIdentifier)--() WHERE NOT psi IN pseudosWithDelete RETURN path" + ) + // create a list for all pseudoidentifiers with no delete call connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoDeleteByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 17(1), the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoDeleteByIdentity.size) + } + + @Test + fun checkComplianceToArticle17_paragraph_2() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" + + ), + listOf(Path(".")), + "MATCH p1=(psi1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT EXISTS { MATCH (psi1)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(h:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest)-[:TO]->(:HttpEndpoint)} AND (hr1.name='DELETE') AND NOT (hr2.name = hr1.name) RETURN p1" + ) + // TODO: DELETE NOTE: Alle pseudo die extern kommunziert werden bekommt man mit folgender query: MATCH p1=(psi1:PseudoIdentifier)--()-[:DFG*]->(:HttpRequest)--()-[:DFG*]->(:HttpRequest) WHERE NOT EXISTS { MATCH (psi1)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(h:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest)-[:TO]->(:HttpEndpoint)} RETURN p1 + // create a list for all pseudoidentifiers, which are communicated to extern with no delete call to extern connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("p1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 17(2), the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.size) + } + + @Test + fun checkComplianceToArticle19() { + + } + + @Test + fun checkComplianceToArticle20_paragraph_1() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" + + ), + listOf(Path(".")), + "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WITH COLLECT(psi) as correctPseudos MATCH p4=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p4" + ) + // create a list for all pseudoidentifiers with no compliant data portability + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("p4") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 20(1), the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) + + + // MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->({name: "HttpStatus.OK"}) return p1 + // Alle nodes die das erfüllen sind compliant: MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->({name: "HttpStatus.OK"}), p2=(m:MemberCallExpression {name:"write"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) return p2 + // Finale Abfrage: MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->({name: "HttpStatus.OK"}), p2=(m:MemberCallExpression {name:"write"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WITH COLLECT(psi) as correctPseudos MATCH p4=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p4 + } + + @Test + fun checkComplianceToArticle20_paragraph_2() { + + } + + // For testing purposes + @Test + fun checkComplianceOfArticle17() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" + + ), + listOf(Path(".")), + "MATCH p=(n:PseudoIdentifier)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'DELETE') RETURN p" + ) + // First we have to check collect all pseudoidentifiers in code. + // Second we have to check whether all pseduoidentifiers can be deleted. + // => This means we have to check every path (Pseudoidentifer => Delete database operation) + + // Second part done + result.forEach { + var path = it.get("p") as Array<*> + // the first node should be the label + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + // Ein Path besteht immer aus Segmenten. 1. Segment => verbunden mit 2. Segment usw... + // Folgendes print statement kann man verwenden, um die endLine rauszubekommen, bei welcher das Data label versehen wurde + //print((path.first() as InternalPath.SelfContainedSegment).end().get("endLine")) + assert(firstNode.labels().contains("PseudoIdentifier")) + // the last node should be a DatabaseQuery + val lastNode = (path.last() as InternalPath.SelfContainedSegment).end() + assert(lastNode.labels().contains("DatabaseQuery")) + // lastNode should be a deleting operation + if (lastNode.get("type").equals("DELETE")) { + // Evtl query für labelNode schreiben und dann diese ausgeben (relationship labeled node...) + // Wie bekomme ich speziellen Knoten aus der List? + } + + // Remove from method get() generated character '"' and generate enum type + val typeOfDatabaseQuery = DatabaseQueryType.valueOf(lastNode.get("type").toString().replace("\"", "")) + assertEquals(DatabaseQueryType.DELETE, typeOfDatabaseQuery, "No deleting operation can be found") + } + +// result.first().apply { +// var path = this.get("p") as Array<*> +// // the first node should be the label +// val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() +// assert(firstNode.labels().contains("PseudoIdentifier")) +// // the last node should be a DatabaseQuery +// val lastNode = (path.last() as InternalPath.SelfContainedSegment).end() +// assert(lastNode.labels().contains("DatabaseQuery")) +// // lastNode should be a deleting operation +// assertTrue(lastNode.get("isDeleting").isTrue, "No deleting operation can be found") +// } + } +} From 155c46c5a18cde1c3ab12bac7fa75761f723cb5e Mon Sep 17 00:00:00 2001 From: andreas Date: Fri, 3 Mar 2023 09:57:03 +0100 Subject: [PATCH 05/39] add: enhanced example for data portability with transfer to external service --- .../RightToDataPortability/Python/client.py | 35 +++++++++++++++---- .../RightToDataPortability/Python/server.py | 21 ++++++++++- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py index 8c953d5..845d2f1 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py @@ -3,7 +3,7 @@ import requests import os -def get_own_data_in_machine_readable_format(): +def get_own_data_in_machine_readable_format(transfer = False): url = 'test-online-notepad.com/data' #@PseudoIdentifier personal_data = { @@ -11,11 +11,34 @@ def get_own_data_in_machine_readable_format(): "name": "", "notes": "" } - # get the data from the server - personal_data = requests.get(url, json = personal_data) - f = open("personal_data.json", "w") - f.write(personal_data) - f.close() + if transfer: + url = 'test-online-notepad.com/transfer' + data = { + "receiver_url": "other-test-online-notepad.com/data", + "personal_data": personal_data + } + requests.get(url, json = data) + else: + # get the data from the server + personal_data = requests.get(url, json = personal_data) + f = open("personal_data.json", "w") + f.write(personal_data) + f.close() + +#def transfer_personal_data_to_another_service(): +# url = 'test-online-notepad.com/transfer' +# #@PseudoIdentifier +# personal_data = { +# "username": "testuser", +# "name": "", +# "notes": "" +# } +# data = { +# "receiver_url": "other-test-online-notepad.com/data", +# "personal_data": personal_data +# } +# # send get request to the server, which will then send the data to the specified url +# response = requests.get(url, json = data) if __name__ == '__main__': get_own_data_in_machine_readable_format() diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py index 3c3f21e..978a2ba 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py @@ -20,9 +20,28 @@ def get_data_in_csv_format(): return "Conflict", 409 else: # get the data from the database (mongodb) - user_data = user_db_collection.find_one({"username": data['username']}), 200 + user_data = user_db_collection.find_one({"username": data['username']}) # send the data to the client return user_data, 200 +@app.route("/transfer", methods=['GET']) +def transfer_data_to_another_service(): + req = request.json + data = { + "receiver_url": req['receiver_url'], + "personal_data": req['personal_data'] + } + if user_db_collection.find( { "username": data['personal_data']['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['personal_data']['username']}) + response = requests.put(data['receiver_url'], json = user_data) + if response.status_code == 201: + return "OK", 200 + else: + return "Internal Server Error", 500 + + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 641e44bfd7b57df9a1ac31beabd9985895271a83 Mon Sep 17 00:00:00 2001 From: andreas Date: Fri, 3 Mar 2023 09:57:43 +0100 Subject: [PATCH 06/39] feat: compliance check query for art 20(2) --- .../clouditor/graph/GDPRComplianceChecks.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 2c10c66..7cb6ca5 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -144,7 +144,33 @@ open class GDPRComplianceChecks { @Test fun checkComplianceToArticle20_paragraph_2() { + // query: MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->(hr2:HttpRequest {name: "PUT"}) WHERE NOT EXISTS {MATCH p2=(hr2)--()-[:DFG*]->(he2:HttpEndpoint)} WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p3 + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" + ), + listOf(Path(".")), + "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->(hr2:HttpRequest {name: \"PUT\"}) WHERE NOT EXISTS {MATCH p2=(hr2)--()-[:DFG*]->(he2:HttpEndpoint)} WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p3" + ) + // create a list for all pseudoidentifiers with no compliant data portability (to external service) + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("p3") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 20(2), the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size) } // For testing purposes From cc9f1d74e7cf45b34e3388ec19e7cd9535c43201 Mon Sep 17 00:00:00 2001 From: andreas Date: Sat, 11 Mar 2023 12:42:26 +0100 Subject: [PATCH 07/39] fix: adjusted config.yml for detection of correct application --- .../RightToErasure/Python/config.yml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml index cb48069..1514310 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml @@ -1,17 +1,6 @@ services: - type: server - directory: server name: server host: test-online-notepad.com - - type: db - directory: server - name: postgres - storages: - - userdata - - otherdata - - type: db - directory: server - name: mongo - storages: - - userdata - - otherdata \ No newline at end of file + - type: client + name: client \ No newline at end of file From 0f1f5f7a3009db2fba606c5b75ea8995136bb627 Mon Sep 17 00:00:00 2001 From: andreas Date: Sat, 11 Mar 2023 12:43:07 +0100 Subject: [PATCH 08/39] feat: created article 19 checks and adjusted other checks --- .../clouditor/graph/GDPRComplianceChecks.kt | 72 +++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 7cb6ca5..08b3937 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -20,7 +20,7 @@ open class GDPRComplianceChecks { ), listOf(Path(".")), - "MATCH p=(n:PseudoIdentifier)--()-[:DFG*]->(hr:HttpRequest)--()-[:DFG*]->(he:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'UPDATE') AND (hr.name = 'PUT') AND (he.method = 'PUT') WITH COLLECT(n) as pseudosWithUpdate MATCH path=(psi:PseudoIdentifier)--() WHERE NOT psi IN pseudosWithUpdate RETURN path" + "MATCH path1=(psi1:PseudoIdentifier)--()-[:DFG*]->(hr:HttpRequest)--()-[:DFG*]->(he:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'UPDATE') AND (hr.name = 'PUT') AND (he.method = 'PUT') WITH COLLECT(psi1) as pseudosWithUpdate MATCH path2=(psi2:PseudoIdentifier)--() WHERE NOT psi2 IN pseudosWithUpdate RETURN path2" ) // create a list for all pseudoidentifiers with no update call connected to them via a data flow @@ -81,14 +81,13 @@ open class GDPRComplianceChecks { ), listOf(Path(".")), - "MATCH p1=(psi1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT EXISTS { MATCH (psi1)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(h:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest)-[:TO]->(:HttpEndpoint)} AND (hr1.name='DELETE') AND NOT (hr2.name = hr1.name) RETURN p1" + "MATCH (hr1:HttpRequest) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr2:HttpRequest {name: 'DELETE'})-[:TO]->(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(hr1) WHERE (hr1.name<>'DELETE') RETURN path1" ) - // TODO: DELETE NOTE: Alle pseudo die extern kommunziert werden bekommt man mit folgender query: MATCH p1=(psi1:PseudoIdentifier)--()-[:DFG*]->(:HttpRequest)--()-[:DFG*]->(:HttpRequest) WHERE NOT EXISTS { MATCH (psi1)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(h:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest)-[:TO]->(:HttpEndpoint)} RETURN p1 // create a list for all pseudoidentifiers, which are communicated to extern with no delete call to extern connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { - var path = it.get("p1") as Array<*> + var path = it.get("path1") as Array<*> // the first node is the pseudoidentifier because of the query val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() @@ -104,6 +103,60 @@ open class GDPRComplianceChecks { @Test fun checkComplianceToArticle19() { + val result_data_flows = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" + + ), + listOf(Path(".")), + "MATCH (hr1:HttpRequest) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr2:HttpRequest)-[:TO]->(he2:HttpEndpoint)--()-[:DFG*]-(hr1) WHERE NOT (hr1.name='DELETE') AND NOT (hr1.name='PUT') AND ((hr2.name='DELETE') OR (hr2.name='PUT')) RETURN path1" + ) + // create a list for all pseudoidentifiers, which are communicated to extern with no delete or update call to extern connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity = mutableListOf() + // iterate over all paths and add to the list + result_data_flows.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 19, the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.size) + + val result_data_storage = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" + + ), + listOf(Path(".")), + "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr1:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" + ) + // create a list for all personal data recipients, which are not mentioned in the information about the data recipients + val listOfAllPersonalDataRecipientsNotMentionedInInformation = mutableListOf() + // iterate over all paths and add to the list + result_data_storage.forEach { + var path = it.get("p3") as Array<*> + + // the first node is the literal, which contains the name of the personal data recipient + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("Literal")) { + val nameOfPersonalDataRecipient = firstNode.get("name").toString() + // add the literal to the list if it is not already in it + if (!listOfAllPersonalDataRecipientsNotMentionedInInformation.contains(nameOfPersonalDataRecipient)) + listOfAllPersonalDataRecipientsNotMentionedInInformation.add(nameOfPersonalDataRecipient) + } + } + // if the code is compliant to article 19, the list should be empty aswell + assertEquals(0, listOfAllPersonalDataRecipientsNotMentionedInInformation.size) } @@ -135,16 +188,10 @@ open class GDPRComplianceChecks { } // if the code is compliant to article 20(1), the list should be empty assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) - - - // MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->({name: "HttpStatus.OK"}) return p1 - // Alle nodes die das erfüllen sind compliant: MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->({name: "HttpStatus.OK"}), p2=(m:MemberCallExpression {name:"write"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) return p2 - // Finale Abfrage: MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->({name: "HttpStatus.OK"}), p2=(m:MemberCallExpression {name:"write"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WITH COLLECT(psi) as correctPseudos MATCH p4=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p4 } @Test fun checkComplianceToArticle20_paragraph_2() { - // query: MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:"READ"})--()-[:DFG*]->(hr2:HttpRequest {name: "PUT"}) WHERE NOT EXISTS {MATCH p2=(hr2)--()-[:DFG*]->(he2:HttpEndpoint)} WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p3 val result = executePPGAndQuery( Path( @@ -153,7 +200,7 @@ open class GDPRComplianceChecks { ), listOf(Path(".")), - "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->(hr2:HttpRequest {name: \"PUT\"}) WHERE NOT EXISTS {MATCH p2=(hr2)--()-[:DFG*]->(he2:HttpEndpoint)} WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p3" + "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->(hr2:HttpRequest {name: \"PUT\"}) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--() WHERE NOT psi2 IN correctPseudos RETURN p3" ) // create a list for all pseudoidentifiers with no compliant data portability (to external service) val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity = mutableListOf() @@ -173,9 +220,10 @@ open class GDPRComplianceChecks { assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size) } - // For testing purposes + // TODO: remove For testing purposes @Test fun checkComplianceOfArticle17() { + return val result = executePPGAndQuery( Path( From b9c236c0015b80f0d2e104178473d003820c8818 Mon Sep 17 00:00:00 2001 From: andreas Date: Sat, 11 Mar 2023 12:43:41 +0100 Subject: [PATCH 09/39] feat: created testcase for article 19 compliance check --- .../NotificationObligation/Python/README.md | 1 + .../NotificationObligation/Python/client.py | 28 +++++++++++++ .../NotificationObligation/Python/config.yml | 9 +++++ .../NotificationObligation/Python/server.py | 40 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py new file mode 100755 index 0000000..8913865 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import requests + +def delete_data(personal_data): + url = 'test-online-notepad.com/data' + requests.delete(url, json = personal_data) + +def rectify_data(personal_data): + url = 'test-online-notepad.com/data' + requests.put(url, json = personal_data) + +def get_information_about_data_recipients(): + data_recipients_information = "receiver of your personal data: ext-ad-server.com/data (external advertising server)\nIt is used for the following purposes: advertising" + # create file containing the information + f = open("data_recipients_information.txt", "w") + f.write(data_recipients_information) + f.close() + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "notes": ["note1", "note2", "note3"] + } + rectify_data(personal_data) + get_information_about_data_recipients() + delete_data(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml new file mode 100644 index 0000000..bd17c02 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml @@ -0,0 +1,9 @@ +services: + - type: server + name: server + host: test-online-notepad.com + - type: client + name: client + - type: external-advertising-server + name: external-advertising-server + host: ext-ad-server.com \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py new file mode 100755 index 0000000..aeb8bb5 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['DELETE']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + + user_db_collection.delete_one({"username": data['username']}) + # inform external advertising server about the deletion + requests.delete("ext-ad-server.com/data", json = data) + return "OK", 200 + +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + url = "ext-ad-server.com/data" + user_db_collection.update_one({"username": data['username']}, {"$set": {"notes": data['notes']}}) + # inform external advertising server about the rectification + requests.put(url, json = data) + return "OK", 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 227b94c03bf6444974c7a654bf12edd67f48f4aa Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 10:39:55 +0100 Subject: [PATCH 10/39] add: adjusted notification obligation testcase --- .../NotificationObligation/Python/client.py | 5 +++++ .../NotificationObligation/Python/server.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py index 8913865..0c456cd 100755 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py @@ -17,12 +17,17 @@ def get_information_about_data_recipients(): f.write(data_recipients_information) f.close() +def send_data_to_server(personal_data): + url = 'test-online-notepad.com/data' + requests.post(url, json = personal_data) + if __name__ == '__main__': #@PseudoIdentifier personal_data = { "username": "testuser", "notes": ["note1", "note2", "note3"] } + send_data_to_server(personal_data) rectify_data(personal_data) get_information_about_data_recipients() delete_data(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py index aeb8bb5..a57a449 100755 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py @@ -36,5 +36,17 @@ def parse_data(): requests.put(url, json = data) return "OK", 200 +@app.route("/data", methods=['POST']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + url = "ext-ad-server.com/data" + # send notes to external advertising server + requests.post("ext-ad-server.com/data", json = data['notes']) + return "OK", 200 + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 9b1f6e4dec0c81eb6f8e5ebf0ae286147d86c3f7 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 10:42:02 +0100 Subject: [PATCH 11/39] feat: created validation testcase of art 19 (notification obligation) --- .../Python_validation/README.md | 1 + .../Python_validation/client.py | 34 +++++++++++++ .../Python_validation/config.yml | 9 ++++ .../Python_validation/server.py | 49 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/client.py new file mode 100755 index 0000000..42d01d7 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/client.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import requests + +def delete_data(personal_data): + url = 'test-online-notepad.com/data' + requests.delete(url, json = personal_data) + +def rectify_data(personal_data): + url = 'test-online-notepad.com/data' + requests.put(url, json = personal_data) + +def get_information_about_data_recipients(): + # VALIDATION: no correct information is given + data_recipients_information = "receiver of your personal data: test-online-notepad.com/data (external advertising server)\nIt is used for the following purposes: advertising" + # create file containing the information + f = open("data_recipients_information.txt", "w") + f.write(data_recipients_information) + f.close() + +def send_data_to_server(personal_data): + url = 'test-online-notepad.com/data' + requests.post(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "notes": ["note1", "note2", "note3"] + } + send_data_to_server(personal_data) + rectify_data(personal_data) + get_information_about_data_recipients() + delete_data(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml new file mode 100644 index 0000000..bd17c02 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml @@ -0,0 +1,9 @@ +services: + - type: server + name: server + host: test-online-notepad.com + - type: client + name: client + - type: external-advertising-server + name: external-advertising-server + host: ext-ad-server.com \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py new file mode 100755 index 0000000..6689fbb --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['DELETE']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + + user_db_collection.delete_one({"username": data['username']}) + # VALIDATION: no external advertising server is informed about the deletion + return "OK", 200 + +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + user_db_collection.update_one({"username": data['username']}, {"$set": {"notes": data['notes']}}) + # VALIDATION: no external advertising server is informed about the rectification + return "OK", 200 + +@app.route("/data", methods=['POST']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + url = "ext-ad-server.com/data" + # send notes to external advertising server + requests.post(url, json = data['notes']) + return "OK", 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 5459469cd2b803abf117d0dd4b865f60b09338b9 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 11:16:28 +0100 Subject: [PATCH 12/39] delete: code clean up --- .../RightToDataPortability/Python/client.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py index 845d2f1..8cddfbd 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py @@ -25,20 +25,5 @@ def get_own_data_in_machine_readable_format(transfer = False): f.write(personal_data) f.close() -#def transfer_personal_data_to_another_service(): -# url = 'test-online-notepad.com/transfer' -# #@PseudoIdentifier -# personal_data = { -# "username": "testuser", -# "name": "", -# "notes": "" -# } -# data = { -# "receiver_url": "other-test-online-notepad.com/data", -# "personal_data": personal_data -# } -# # send get request to the server, which will then send the data to the specified url -# response = requests.get(url, json = data) - if __name__ == '__main__': get_own_data_in_machine_readable_format() From 3e40dd42c745cc649369d0ffd22ded9d8ecac19f Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 11:41:07 +0100 Subject: [PATCH 13/39] feat: created validation testcase for Art. 20 (right for data portability) --- .../Python_validation/README.md | 1 + .../Python_validation/client.py | 29 ++++++++++++ .../Python_validation/config.yml | 17 +++++++ .../Python_validation/server.py | 44 +++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py new file mode 100755 index 0000000..b6369b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +import requests +import os + +def get_own_data_in_machine_readable_format(transfer = False): + url = 'test-online-notepad.com/data' + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "", + "notes": "" + } + if transfer: + url = 'test-online-notepad.com/transfer' + data = { + "receiver_url": "other-test-online-notepad.com/data", + "personal_data": personal_data + } + requests.get(url, json = data) + else: + # get the data from the server + personal_data = requests.get(url, json = personal_data) + f = open("personal_data.json", "w") + # VALIDATION: Data is not stored on the client => personal_data.json is empty + f.close() + +if __name__ == '__main__': + get_own_data_in_machine_readable_format() diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml new file mode 100644 index 0000000..cb48069 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml @@ -0,0 +1,17 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + directory: server + name: mongo + storages: + - userdata + - otherdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py new file mode 100755 index 0000000..3c7412a --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['GET']) +def get_data_in_csv_format(): + req = request.json + data = { + "username": req['username'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['username']}) + # send the data to the client + return user_data, 200 + +@app.route("/transfer", methods=['GET']) +def transfer_data_to_another_service(): + req = request.json + data = { + "receiver_url": req['receiver_url'], + "personal_data": req['personal_data'] + } + if user_db_collection.find( { "username": data['personal_data']['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['personal_data']['username']}) + # VALIDATION: Data is not transfered to external service + return "OK", 200 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 1ce4f7eaf5d85536d15e4d425ce48e6a66be8de8 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 11:53:35 +0100 Subject: [PATCH 14/39] feat: created testcase for Art. 16 validation --- .../Python_validation/README.md | 1 + .../Python_validation/client.py | 17 ++++++++++++ .../Python_validation/config.yml | 17 ++++++++++++ .../Python_validation/server.py | 27 +++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py new file mode 100755 index 0000000..b58a773 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import requests + +def rectify(changed_name): + url = 'test-online-notepad.com/data' + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "firstname lastname", + "notes": ["note1", "note2", "note3"] + } + personal_data["name"] = changed_name + requests.put(url, json = personal_data) + +if __name__ == '__main__': + rectify("New Name") diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml new file mode 100644 index 0000000..cb48069 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml @@ -0,0 +1,17 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + directory: server + name: mongo + storages: + - userdata + - otherdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py new file mode 100755 index 0000000..c3f2d2f --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "name": req['name'], + "notes": req['notes'] + } + if user_db_collection.find( { "name": data['name'] } ).count() > 0: + return "Conflict", 409 + else: + # VALIDATION: no rectification is performed => No update call to the database + return "Created", 201 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 06fc56543bb6fe3d04a27a5e258b4008b6a3c015 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 11:53:56 +0100 Subject: [PATCH 15/39] feat: created validation testcase for At. 17 --- .../Python_validation/README.md | 1 + .../Python_validation/client.py | 15 +++++++++++ .../Python_validation/config.yml | 6 +++++ .../Python_validation/server.py | 27 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md new file mode 100644 index 0000000..d9f25b6 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md @@ -0,0 +1 @@ +TODO: Description \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py new file mode 100755 index 0000000..2a1578f --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import requests + +def delete_own_data(): + url = 'test-online-notepad.com/data' + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "notes": ["note1", "note2", "note3"] + } + requests.delete(url, json = personal_data) + +if __name__ == '__main__': + delete_own_data() diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml new file mode 100644 index 0000000..1514310 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml @@ -0,0 +1,6 @@ +services: + - type: server + name: server + host: test-online-notepad.com + - type: client + name: client \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py new file mode 100755 index 0000000..1071df5 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['DELETE']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # VALIDATION: no external advertising server is informed about the deletion and no deletion is performed + return "OK", 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 1cfe355913f7e6e8b5809e98a05f716bf3ddec61 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 16:44:20 +0100 Subject: [PATCH 16/39] add: updated python validation for Art. 17 --- .../RightToErasure/Python_validation/client.py | 15 +++++++++++---- .../RightToErasure/Python_validation/server.py | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py index 2a1578f..f32dcd9 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py @@ -2,14 +2,21 @@ import requests -def delete_own_data(): +def delete_own_data(personal_data): url = 'test-online-notepad.com/data' + requests.delete(url, json = personal_data) + +def send_data_to_external_advertising_server(personal_data): + url = 'test-online-notepad.com/data' + requests.post(url, json = personal_data) + + +if __name__ == '__main__': #@PseudoIdentifier personal_data = { "username": "testuser", "notes": ["note1", "note2", "note3"] } - requests.delete(url, json = personal_data) + send_data_to_external_advertising_server(personal_data) + delete_own_data(personal_data) -if __name__ == '__main__': - delete_own_data() diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py index 1071df5..bb03e7e 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py @@ -23,5 +23,22 @@ def parse_data(): # VALIDATION: no external advertising server is informed about the deletion and no deletion is performed return "OK", 200 +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + # send data to external advertising server + url = 'test-online-notepad.com/data' + requests.put(url, json = data) + return "OK", 200 + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From 4f74c105ba1890e3390dec8bdc62ba9ec900e063 Mon Sep 17 00:00:00 2001 From: andreas Date: Thu, 16 Mar 2023 16:44:49 +0100 Subject: [PATCH 17/39] feat: created validation testcases and updated queries --- .../clouditor/graph/GDPRComplianceChecks.kt | 381 +++++++++++++++--- 1 file changed, 324 insertions(+), 57 deletions(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 08b3937..89f901f 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -3,6 +3,7 @@ package io.clouditor.graph import io.clouditor.graph.utils.DatabaseQueryType import kotlin.io.path.Path import kotlin.test.assertEquals +import kotlin.test.assertNotEquals import org.junit.Test import org.junit.jupiter.api.Tag import org.neo4j.driver.internal.InternalPath @@ -16,18 +17,18 @@ open class GDPRComplianceChecks { executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" - + "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" ), listOf(Path(".")), - "MATCH path1=(psi1:PseudoIdentifier)--()-[:DFG*]->(hr:HttpRequest)--()-[:DFG*]->(he:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'UPDATE') AND (hr.name = 'PUT') AND (he.method = 'PUT') WITH COLLECT(psi1) as pseudosWithUpdate MATCH path2=(psi2:PseudoIdentifier)--() WHERE NOT psi2 IN pseudosWithUpdate RETURN path2" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" ) - // create a list for all pseudoidentifiers with no update call connected to them via a data flow + // create a list for all pseudoidentifiers with no update call connected to them via a data + // flow val listOfAllPseudoIdentifierWithNoUpdateByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { - var path = it.get("path") as Array<*> + var path = it.get("path1") as Array<*> // the first node is the pseudoidentifier because of the query val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() @@ -42,22 +43,53 @@ open class GDPRComplianceChecks { } @Test - fun checkComplianceToArticle17_paragraph_1() { + fun checkComplianceToArticle16_validation() { val result = executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" + "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation" + ), + listOf(Path(".")), + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" + ) + + // create a list for all pseudoidentifiers with no update call connected to them via a data + // flow + val listOfAllPseudoIdentifierWithNoUpdateByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoUpdateByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoUpdateByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 16, the list should be empty + assertNotEquals(0, listOfAllPseudoIdentifierWithNoUpdateByIdentity.size) + } + @Test + fun checkComplianceToArticle17_paragraph_1() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" ), listOf(Path(".")), - "MATCH p=(n:PseudoIdentifier)--()-[:DFG*]->(hr:HttpRequest)--()-[:DFG*]->(he:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'DELETE') AND (hr.name = 'DELETE') AND (he.method = 'DELETE') WITH COLLECT(n) as pseudosWithDelete MATCH path=(psi:PseudoIdentifier)--() WHERE NOT psi IN pseudosWithDelete RETURN path" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" ) - // create a list for all pseudoidentifiers with no delete call connected to them via a data flow + // create a list for all pseudoidentifiers with no delete call connected to them via a data + // flow val listOfAllPseudoIdentifierWithNoDeleteByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { - var path = it.get("path") as Array<*> + var path = it.get("path1") as Array<*> // the first node is the pseudoidentifier because of the query val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() @@ -72,18 +104,48 @@ open class GDPRComplianceChecks { } @Test - fun checkComplianceToArticle17_paragraph_2() { + fun checkComplianceToArticle17_paragraph_1_validation() { val result = executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation" + ), + listOf(Path(".")), + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" + ) + // create a list for all pseudoidentifiers with no delete call connected to them via a data + // flow + val listOfAllPseudoIdentifierWithNoDeleteByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path1") as Array<*> + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoDeleteByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 17(1), the list should be empty + assertNotEquals(0, listOfAllPseudoIdentifierWithNoDeleteByIdentity.size) + } + + @Test + fun checkComplianceToArticle17_paragraph_2() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" ), listOf(Path(".")), - "MATCH (hr1:HttpRequest) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr2:HttpRequest {name: 'DELETE'})-[:TO]->(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(hr1) WHERE (hr1.name<>'DELETE') RETURN path1" + "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(hr1) WHERE (hr1.name='DELETE') } RETURN path1" ) - // create a list for all pseudoidentifiers, which are communicated to extern with no delete call to extern connected to them via a data flow + // create a list for all pseudoidentifiers, which are communicated to extern with no delete + // call to extern connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { @@ -93,7 +155,10 @@ open class GDPRComplianceChecks { val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() if (firstNode.labels().contains("PseudoIdentifier")) { // add the pseudoidentifier to the list if it is not already in it - if (!listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.contains(firstNode.id())) + if (!listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.contains( + firstNode.id() + ) + ) listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.add(firstNode.id()) } } @@ -101,19 +166,52 @@ open class GDPRComplianceChecks { assertEquals(0, listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.size) } + @Test + fun checkComplianceToArticle17_paragraph_2_validation() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation" + ), + listOf(Path(".")), + "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(hr1) WHERE (hr1.name='DELETE') } RETURN path1" + ) + // create a list for all pseudoidentifiers, which are communicated to extern with no delete + // call to extern connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 17(2), the list should be empty + assertNotEquals(0, listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.size) + } + @Test fun checkComplianceToArticle19() { val result_data_flows = executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" - + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" ), listOf(Path(".")), - "MATCH (hr1:HttpRequest) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr2:HttpRequest)-[:TO]->(he2:HttpEndpoint)--()-[:DFG*]-(hr1) WHERE NOT (hr1.name='DELETE') AND NOT (hr1.name='PUT') AND ((hr2.name='DELETE') OR (hr2.name='PUT')) RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" ) - // create a list for all pseudoidentifiers, which are communicated to extern with no delete or update call to extern connected to them via a data flow + // create a list for all pseudoidentifiers, which are communicated to extern with no delete + // or update call to extern connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity = mutableListOf() // iterate over all paths and add to the list result_data_flows.forEach { @@ -123,8 +221,13 @@ open class GDPRComplianceChecks { val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() if (firstNode.labels().contains("PseudoIdentifier")) { // add the pseudoidentifier to the list if it is not already in it - if (!listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.contains(firstNode.id())) - listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.add(firstNode.id()) + if (!listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.add( + firstNode.id() + ) } } // if the code is compliant to article 19, the list should be empty @@ -134,13 +237,13 @@ open class GDPRComplianceChecks { executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" - + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" ), listOf(Path(".")), - "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr1:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" + "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name CONTAINS \".com\") AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" ) - // create a list for all personal data recipients, which are not mentioned in the information about the data recipients + // create a list for all personal data recipients, which are not mentioned in the + // information about the data recipients val listOfAllPersonalDataRecipientsNotMentionedInInformation = mutableListOf() // iterate over all paths and add to the list result_data_storage.forEach { @@ -151,13 +254,85 @@ open class GDPRComplianceChecks { if (firstNode.labels().contains("Literal")) { val nameOfPersonalDataRecipient = firstNode.get("name").toString() // add the literal to the list if it is not already in it - if (!listOfAllPersonalDataRecipientsNotMentionedInInformation.contains(nameOfPersonalDataRecipient)) - listOfAllPersonalDataRecipientsNotMentionedInInformation.add(nameOfPersonalDataRecipient) + if (!listOfAllPersonalDataRecipientsNotMentionedInInformation.contains( + nameOfPersonalDataRecipient + ) + ) + listOfAllPersonalDataRecipientsNotMentionedInInformation.add( + nameOfPersonalDataRecipient + ) } } // if the code is compliant to article 19, the list should be empty aswell assertEquals(0, listOfAllPersonalDataRecipientsNotMentionedInInformation.size) + } + + @Test + fun checkComplianceToArticle19_validation() { + val result_data_flows = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation" + ), + listOf(Path(".")), + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" + ) + // create a list for all pseudoidentifiers, which are communicated to extern with no delete + // or update call to extern connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity = mutableListOf() + // iterate over all paths and add to the list + result_data_flows.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.add( + firstNode.id() + ) + } + } + // check if the code is not compliant to article 19 + assertNotEquals(0, listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.size) + + val result_data_storage = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation" + ), + listOf(Path(".")), + "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name CONTAINS \".com\") AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" + ) + // create a list for all personal data recipients, which are not mentioned in the + // information about the data recipients + val listOfAllPersonalDataRecipientsNotMentionedInInformation = mutableListOf() + // iterate over all paths and add to the list + result_data_storage.forEach { + var path = it.get("p3") as Array<*> + // the first node is the literal, which contains the name of the personal data recipient + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("Literal")) { + val nameOfPersonalDataRecipient = firstNode.get("name").toString() + // add the literal to the list if it is not already in it + if (!listOfAllPersonalDataRecipientsNotMentionedInInformation.contains( + nameOfPersonalDataRecipient + ) + ) + listOfAllPersonalDataRecipientsNotMentionedInInformation.add( + nameOfPersonalDataRecipient + ) + } + } + // check if the code is not compliant to article 19 + assertNotEquals(0, listOfAllPersonalDataRecipientsNotMentionedInInformation.size) } @Test @@ -166,14 +341,14 @@ open class GDPRComplianceChecks { executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" - + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WITH COLLECT(psi) as correctPseudos MATCH p4=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p4" ) // create a list for all pseudoidentifiers with no compliant data portability - val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = mutableListOf() + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = + mutableListOf() // iterate over all paths and add to the list result.forEach { var path = it.get("p4") as Array<*> @@ -182,28 +357,106 @@ open class GDPRComplianceChecks { val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() if (firstNode.labels().contains("PseudoIdentifier")) { // add the pseudoidentifier to the list if it is not already in it - if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.contains(firstNode.id())) - listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.add(firstNode.id()) + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.add( + firstNode.id() + ) } } // if the code is compliant to article 20(1), the list should be empty assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) } + @Test + fun checkComplianceToArticle20_paragraph_1_validation() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" + ), + listOf(Path(".")), + "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WITH COLLECT(psi) as correctPseudos MATCH p4=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p4" + ) + // create a list for all pseudoidentifiers with no compliant data portability + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = + mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("p4") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.add( + firstNode.id() + ) + } + } + // if the code is compliant to article 20(1), the list should be empty + assertNotEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) + } + @Test fun checkComplianceToArticle20_paragraph_2() { val result = executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" + ), + listOf(Path(".")), + "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->(hr2:HttpRequest {name: \"PUT\"}) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--() WHERE NOT psi2 IN correctPseudos RETURN p3" + ) + // create a list for all pseudoidentifiers with no compliant data portability (to external + // service) + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity = + mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("p3") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity + .contains(firstNode.id()) + ) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity + .add(firstNode.id()) + } + } + // if the code is compliant to article 20(2), the list should be empty + assertEquals( + 0, + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size + ) + } + @Test + fun checkComplianceToArticle20_paragraph_2_validation() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->(hr2:HttpRequest {name: \"PUT\"}) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--() WHERE NOT psi2 IN correctPseudos RETURN p3" ) - // create a list for all pseudoidentifiers with no compliant data portability (to external service) - val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity = mutableListOf() + // create a list for all pseudoidentifiers with no compliant data portability (to external + // service) + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity = + mutableListOf() // iterate over all paths and add to the list result.forEach { var path = it.get("p3") as Array<*> @@ -212,12 +465,18 @@ open class GDPRComplianceChecks { val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() if (firstNode.labels().contains("PseudoIdentifier")) { // add the pseudoidentifier to the list if it is not already in it - if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.contains(firstNode.id())) - listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.add(firstNode.id()) + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity + .contains(firstNode.id()) + ) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity + .add(firstNode.id()) } } // if the code is compliant to article 20(2), the list should be empty - assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size) + assertNotEquals( + 0, + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size + ) } // TODO: remove For testing purposes @@ -228,15 +487,15 @@ open class GDPRComplianceChecks { executePPGAndQuery( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" - + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" ), listOf(Path(".")), "MATCH p=(n:PseudoIdentifier)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'DELETE') RETURN p" ) // First we have to check collect all pseudoidentifiers in code. // Second we have to check whether all pseduoidentifiers can be deleted. - // => This means we have to check every path (Pseudoidentifer => Delete database operation) + // => This means we have to check every path (Pseudoidentifer => Delete database + // operation) // Second part done result.forEach { @@ -244,33 +503,41 @@ open class GDPRComplianceChecks { // the first node should be the label val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() // Ein Path besteht immer aus Segmenten. 1. Segment => verbunden mit 2. Segment usw... - // Folgendes print statement kann man verwenden, um die endLine rauszubekommen, bei welcher das Data label versehen wurde - //print((path.first() as InternalPath.SelfContainedSegment).end().get("endLine")) + // Folgendes print statement kann man verwenden, um die endLine rauszubekommen, bei + // welcher das Data label versehen wurde + // print((path.first() as InternalPath.SelfContainedSegment).end().get("endLine")) assert(firstNode.labels().contains("PseudoIdentifier")) // the last node should be a DatabaseQuery val lastNode = (path.last() as InternalPath.SelfContainedSegment).end() assert(lastNode.labels().contains("DatabaseQuery")) // lastNode should be a deleting operation if (lastNode.get("type").equals("DELETE")) { - // Evtl query für labelNode schreiben und dann diese ausgeben (relationship labeled node...) + // Evtl query für labelNode schreiben und dann diese ausgeben (relationship labeled + // node...) // Wie bekomme ich speziellen Knoten aus der List? } // Remove from method get() generated character '"' and generate enum type - val typeOfDatabaseQuery = DatabaseQueryType.valueOf(lastNode.get("type").toString().replace("\"", "")) - assertEquals(DatabaseQueryType.DELETE, typeOfDatabaseQuery, "No deleting operation can be found") + val typeOfDatabaseQuery = + DatabaseQueryType.valueOf(lastNode.get("type").toString().replace("\"", "")) + assertEquals( + DatabaseQueryType.DELETE, + typeOfDatabaseQuery, + "No deleting operation can be found" + ) } -// result.first().apply { -// var path = this.get("p") as Array<*> -// // the first node should be the label -// val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() -// assert(firstNode.labels().contains("PseudoIdentifier")) -// // the last node should be a DatabaseQuery -// val lastNode = (path.last() as InternalPath.SelfContainedSegment).end() -// assert(lastNode.labels().contains("DatabaseQuery")) -// // lastNode should be a deleting operation -// assertTrue(lastNode.get("isDeleting").isTrue, "No deleting operation can be found") -// } + // result.first().apply { + // var path = this.get("p") as Array<*> + // // the first node should be the label + // val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + // assert(firstNode.labels().contains("PseudoIdentifier")) + // // the last node should be a DatabaseQuery + // val lastNode = (path.last() as InternalPath.SelfContainedSegment).end() + // assert(lastNode.labels().contains("DatabaseQuery")) + // // lastNode should be a deleting operation + // assertTrue(lastNode.get("isDeleting").isTrue, "No deleting operation can be + // found") + // } } } From 85006b7457590b25338e8cb53392370cca3c5f9b Mon Sep 17 00:00:00 2001 From: andreas Date: Mon, 20 Mar 2023 11:09:11 +0100 Subject: [PATCH 18/39] add: database query detection support for other libraries --- .../graph/passes/GormDatabasePass.kt | 11 ++++- .../graph/passes/python/Psycopg2Pass.kt | 11 ++++- .../graph/passes/python/PyMongoPass.kt | 46 ++++++++++++++++--- .../graph/utils/DatabaseQueryType.kt | 8 +++- 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt index 4792c06..f877ccc 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt @@ -117,7 +117,16 @@ class GormDatabasePass : DatabaseOperationPass() { val op = app?.functionalities?.filterIsInstance()?.firstOrNull()?.let { - val op = createDatabaseQuery(result, false, it, mutableListOf(), calls, app, DatabaseQueryType.READ) + val op = + createDatabaseQuery( + result, + false, + it, + mutableListOf(), + calls, + app, + DatabaseQueryType.READ + ) op.name = call.name // loop through the calls and set DFG edges diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt index 58ff43d..569133a 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt @@ -149,7 +149,16 @@ class Psycopg2Pass : DatabaseOperationPass() { val dbName = dbStorage.firstOrNull()?.name val storage = connect.to.map { it.getStorageOrCreate(table ?: "", dbName) } - val op = createDatabaseQuery(result, false, connect, storage, mutableListOf(call), app, DatabaseQueryType.UNKNOWN) + val op = + createDatabaseQuery( + result, + false, + connect, + storage, + mutableListOf(call), + app, + DatabaseQueryType.UNKNOWN + ) op.name = call.name // in the select case, the arguments are just arguments to the query itself and flow diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt index 0fac523..e76bdb2 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt @@ -169,14 +169,32 @@ class PyMongoPass : DatabaseOperationPass() { var (connect, storage) = pair var op: DatabaseQuery? = null if (mce.name == "insert_one") { - op = createDatabaseQuery(t, true, connect, storage, listOf(mce), app, DatabaseQueryType.CREATE) + op = + createDatabaseQuery( + t, + true, + connect, + storage, + listOf(mce), + app, + DatabaseQueryType.CREATE + ) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) } if (mce.name == "find" || mce.name == "find_one") { - op = createDatabaseQuery(t, false, connect, storage, listOf(mce), app, DatabaseQueryType.READ) + op = + createDatabaseQuery( + t, + false, + connect, + storage, + listOf(mce), + app, + DatabaseQueryType.READ + ) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) @@ -185,7 +203,16 @@ class PyMongoPass : DatabaseOperationPass() { } if (mce.name == "delete_one" || mce.name == "delete_many") { - op = createDatabaseQuery(t, true, connect, storage, listOf(mce), app, DatabaseQueryType.DELETE) + op = + createDatabaseQuery( + t, + true, + connect, + storage, + listOf(mce), + app, + DatabaseQueryType.DELETE + ) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) @@ -194,7 +221,16 @@ class PyMongoPass : DatabaseOperationPass() { } if (mce.name == "update_one" || mce.name == "update_many") { - op = createDatabaseQuery(t, true, connect, storage, listOf(mce), app, DatabaseQueryType.UPDATE) + op = + createDatabaseQuery( + t, + true, + connect, + storage, + listOf(mce), + app, + DatabaseQueryType.UPDATE + ) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) @@ -205,8 +241,6 @@ class PyMongoPass : DatabaseOperationPass() { if (op != null) { op.name = mce.name } - - } override fun cleanup() { diff --git a/cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt b/cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt index ed72aa7..b2114ad 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/utils/DatabaseQueryType.kt @@ -1,5 +1,9 @@ package io.clouditor.graph.utils enum class DatabaseQueryType { - CREATE, READ, UPDATE, DELETE, UNKNOWN -} \ No newline at end of file + CREATE, + READ, + UPDATE, + DELETE, + UNKNOWN +} From 415c7774417071b805e42c55cdedc0bc205ef4f2 Mon Sep 17 00:00:00 2001 From: andreas Date: Mon, 20 Mar 2023 11:09:35 +0100 Subject: [PATCH 19/39] add: updated testcase for validation of Art. 20 --- .../RightToDataPortability/Python/client.py | 21 ++++++++++------- .../RightToDataPortability/Python/server.py | 14 +++++++++++ .../Python_validation/client.py | 23 +++++++++++-------- .../Python_validation/server.py | 17 +++++++++++++- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py index 8cddfbd..11acc86 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py @@ -3,14 +3,8 @@ import requests import os -def get_own_data_in_machine_readable_format(transfer = False): +def get_own_data_in_machine_readable_format(transfer = False, personal_data=None): url = 'test-online-notepad.com/data' - #@PseudoIdentifier - personal_data = { - "username": "testuser", - "name": "", - "notes": "" - } if transfer: url = 'test-online-notepad.com/transfer' data = { @@ -25,5 +19,16 @@ def get_own_data_in_machine_readable_format(transfer = False): f.write(personal_data) f.close() +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/data' + requests.put(url, json = personal_data) + if __name__ == '__main__': - get_own_data_in_machine_readable_format() + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "", + "notes": "" + } + store_personal_data_on_server(personal_data) + get_own_data_in_machine_readable_format(personal_data=personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py index 978a2ba..9bba498 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py @@ -42,6 +42,20 @@ def transfer_data_to_another_service(): else: return "Internal Server Error", 500 +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + return "OK", 200 + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py index b6369b6..c1ef1ff 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py @@ -3,14 +3,8 @@ import requests import os -def get_own_data_in_machine_readable_format(transfer = False): +def get_own_data_in_machine_readable_format(transfer = False, personal_data=None): url = 'test-online-notepad.com/data' - #@PseudoIdentifier - personal_data = { - "username": "testuser", - "name": "", - "notes": "" - } if transfer: url = 'test-online-notepad.com/transfer' data = { @@ -22,8 +16,19 @@ def get_own_data_in_machine_readable_format(transfer = False): # get the data from the server personal_data = requests.get(url, json = personal_data) f = open("personal_data.json", "w") - # VALIDATION: Data is not stored on the client => personal_data.json is empty + # VALIDATION: Personal data is not stored on the client (write call is missing) f.close() +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/data' + requests.put(url, json = personal_data) + if __name__ == '__main__': - get_own_data_in_machine_readable_format() + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "", + "notes": "" + } + store_personal_data_on_server(personal_data) + get_own_data_in_machine_readable_format(personal_data=personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py index 3c7412a..7569a5c 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py @@ -36,7 +36,22 @@ def transfer_data_to_another_service(): else: # get the data from the database (mongodb) user_data = user_db_collection.find_one({"username": data['personal_data']['username']}) - # VALIDATION: Data is not transfered to external service + # VALIDATION: Personal data is not sent to a third party + return "OK", 200 + + +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) return "OK", 200 From 54e8aacfebcddf47e7d25078b658ece618dba87a Mon Sep 17 00:00:00 2001 From: andreas Date: Mon, 20 Mar 2023 11:10:07 +0100 Subject: [PATCH 20/39] add: adjusted test suite --- .../test/java/io/clouditor/graph/GDPRComplianceChecks.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 89f901f..6d24e1e 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -344,14 +344,14 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), - "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WITH COLLECT(psi) as correctPseudos MATCH p4=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p4" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type=\"CREATE\") OR (d.type=\"UPDATE\") AND NOT EXISTS { MATCH path2=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { - var path = it.get("p4") as Array<*> + val path = it.get("path1") as Array<*> // the first node is the pseudoidentifier because of the query val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() @@ -379,14 +379,14 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), - "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), p3=(m2:MemberCallExpression)--()-[:DFG*]-(:Node)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WITH COLLECT(psi) as correctPseudos MATCH p4=(psi2:PseudoIdentifier)--(:Node) WHERE NOT psi2 IN correctPseudos RETURN p4" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type=\"CREATE\") OR (d.type=\"UPDATE\") AND NOT EXISTS { MATCH path2=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { - var path = it.get("p4") as Array<*> + val path = it.get("path1") as Array<*> // the first node is the pseudoidentifier because of the query val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() From 90feaea9ef73ed1796f47731aa30e0aa9304fd99 Mon Sep 17 00:00:00 2001 From: andreas Date: Tue, 21 Mar 2023 11:32:13 +0100 Subject: [PATCH 21/39] add: adjusted code for testcase of Art.20 --- .../RightToDataPortability/Python/client.py | 37 ++++++++++--------- .../RightToDataPortability/Python/server.py | 2 +- .../Python_validation/client.py | 37 ++++++++++--------- .../Python_validation/server.py | 5 +-- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py index 11acc86..7fc68cf 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py @@ -3,32 +3,33 @@ import requests import os -def get_own_data_in_machine_readable_format(transfer = False, personal_data=None): +def get_personal_data_in_machine_readable_format(personal_data): url = 'test-online-notepad.com/data' - if transfer: - url = 'test-online-notepad.com/transfer' - data = { - "receiver_url": "other-test-online-notepad.com/data", - "personal_data": personal_data - } - requests.get(url, json = data) - else: - # get the data from the server - personal_data = requests.get(url, json = personal_data) - f = open("personal_data.json", "w") - f.write(personal_data) - f.close() + # get the data from the server + personal_data_received = requests.get(url, json = personal_data) + f = open("personal_data.json", "w") + f.write(personal_data_received) + f.close() + +def transfer_personal_data_to_another_service(personal_data): + url = 'test-online-notepad.com/transfer' + data = { + "receiver_url": "other-test-online-notepad.com/data", + "personal_data": personal_data + } + requests.get(url, json = data) def store_personal_data_on_server(personal_data): - url = 'test-online-notepad.com/data' + url = 'test-online-notepad.com/store_data' requests.put(url, json = personal_data) if __name__ == '__main__': #@PseudoIdentifier - personal_data = { + personal_data_of_client = { "username": "testuser", "name": "", "notes": "" } - store_personal_data_on_server(personal_data) - get_own_data_in_machine_readable_format(personal_data=personal_data) + store_personal_data_on_server(personal_data_of_client) + get_personal_data_in_machine_readable_format(personal_data_of_client) + transfer_personal_data_to_another_service(personal_data_of_client) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py index 9bba498..f98e54d 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py @@ -42,7 +42,7 @@ def transfer_data_to_another_service(): else: return "Internal Server Error", 500 -@app.route("/data", methods=['PUT']) +@app.route("/store_data", methods=['PUT']) def parse_data(): req = request.json data = { diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py index c1ef1ff..cdc5a79 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py @@ -3,32 +3,33 @@ import requests import os -def get_own_data_in_machine_readable_format(transfer = False, personal_data=None): +def get_personal_data_in_machine_readable_format(personal_data): url = 'test-online-notepad.com/data' - if transfer: - url = 'test-online-notepad.com/transfer' - data = { - "receiver_url": "other-test-online-notepad.com/data", - "personal_data": personal_data - } - requests.get(url, json = data) - else: - # get the data from the server - personal_data = requests.get(url, json = personal_data) - f = open("personal_data.json", "w") - # VALIDATION: Personal data is not stored on the client (write call is missing) - f.close() + # get the data from the server + personal_data_received = requests.get(url, json = personal_data) + f = open("personal_data.json", "w") + # VALIDATION: The personal data in machine-readable format is not stored on the client + f.close() + +def transfer_personal_data_to_another_service(personal_data): + url = 'test-online-notepad.com/transfer' + data = { + "receiver_url": "other-test-online-notepad.com/data", + "personal_data": personal_data + } + requests.get(url, json = data) def store_personal_data_on_server(personal_data): - url = 'test-online-notepad.com/data' + url = 'test-online-notepad.com/store_data' requests.put(url, json = personal_data) if __name__ == '__main__': #@PseudoIdentifier - personal_data = { + personal_data_of_client = { "username": "testuser", "name": "", "notes": "" } - store_personal_data_on_server(personal_data) - get_own_data_in_machine_readable_format(personal_data=personal_data) + store_personal_data_on_server(personal_data_of_client) + get_personal_data_in_machine_readable_format(personal_data_of_client) + transfer_personal_data_to_another_service(personal_data_of_client) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py index 7569a5c..8e6f193 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py @@ -36,11 +36,10 @@ def transfer_data_to_another_service(): else: # get the data from the database (mongodb) user_data = user_db_collection.find_one({"username": data['personal_data']['username']}) - # VALIDATION: Personal data is not sent to a third party + # VALIDATION: Personal data is not transferred to a third party return "OK", 200 - -@app.route("/data", methods=['PUT']) +@app.route("/store_data", methods=['PUT']) def parse_data(): req = request.json data = { From a515a317ef3f43af3b74469b2550b74d4672fd5f Mon Sep 17 00:00:00 2001 From: andreas Date: Tue, 21 Mar 2023 11:32:35 +0100 Subject: [PATCH 22/39] add: reworked GDPR compliance checks --- .../java/io/clouditor/graph/GDPRComplianceChecks.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 6d24e1e..1918552 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -344,7 +344,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type=\"CREATE\") OR (d.type=\"UPDATE\") AND NOT EXISTS { MATCH path2=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"PUT\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name: \"write\"})-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = @@ -379,7 +379,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type=\"CREATE\") OR (d.type=\"UPDATE\") AND NOT EXISTS { MATCH path2=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-(:Node)--()-[:DFG*]-(hr1), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"PUT\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name: \"write\"})-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = @@ -414,7 +414,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), - "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->(hr2:HttpRequest {name: \"PUT\"}) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--() WHERE NOT psi2 IN correctPseudos RETURN p3" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE ((d1.type=\"CREATE\") OR (d1.type=\"UPDATE\")) AND NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint)} RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability (to external // service) @@ -422,7 +422,7 @@ open class GDPRComplianceChecks { mutableListOf() // iterate over all paths and add to the list result.forEach { - var path = it.get("p3") as Array<*> + var path = it.get("path1") as Array<*> // the first node is the pseudoidentifier because of the query val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() @@ -451,7 +451,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), - "MATCH p1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d:DatabaseQuery {type:\"READ\"})--()-[:DFG*]->(hr2:HttpRequest {name: \"PUT\"}) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(psi) as correctPseudos MATCH p3=(psi2:PseudoIdentifier)--() WHERE NOT psi2 IN correctPseudos RETURN p3" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE ((d1.type=\"CREATE\") OR (d1.type=\"UPDATE\")) AND NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint)} RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability (to external // service) @@ -459,7 +459,7 @@ open class GDPRComplianceChecks { mutableListOf() // iterate over all paths and add to the list result.forEach { - var path = it.get("p3") as Array<*> + var path = it.get("path1") as Array<*> // the first node is the pseudoidentifier because of the query val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() From 60305985cedd027489397ad3b7be047a7e4e633e Mon Sep 17 00:00:00 2001 From: andreas Date: Tue, 21 Mar 2023 11:33:10 +0100 Subject: [PATCH 23/39] delete: removed obsolete code (code clean up) --- .../clouditor/graph/GDPRComplianceChecks.kt | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 1918552..04bef24 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -478,66 +478,4 @@ open class GDPRComplianceChecks { listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size ) } - - // TODO: remove For testing purposes - @Test - fun checkComplianceOfArticle17() { - return - val result = - executePPGAndQuery( - Path( - System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" - ), - listOf(Path(".")), - "MATCH p=(n:PseudoIdentifier)--()-[:DFG*]->(d:DatabaseQuery) WHERE (d.type = 'DELETE') RETURN p" - ) - // First we have to check collect all pseudoidentifiers in code. - // Second we have to check whether all pseduoidentifiers can be deleted. - // => This means we have to check every path (Pseudoidentifer => Delete database - // operation) - - // Second part done - result.forEach { - var path = it.get("p") as Array<*> - // the first node should be the label - val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() - // Ein Path besteht immer aus Segmenten. 1. Segment => verbunden mit 2. Segment usw... - // Folgendes print statement kann man verwenden, um die endLine rauszubekommen, bei - // welcher das Data label versehen wurde - // print((path.first() as InternalPath.SelfContainedSegment).end().get("endLine")) - assert(firstNode.labels().contains("PseudoIdentifier")) - // the last node should be a DatabaseQuery - val lastNode = (path.last() as InternalPath.SelfContainedSegment).end() - assert(lastNode.labels().contains("DatabaseQuery")) - // lastNode should be a deleting operation - if (lastNode.get("type").equals("DELETE")) { - // Evtl query für labelNode schreiben und dann diese ausgeben (relationship labeled - // node...) - // Wie bekomme ich speziellen Knoten aus der List? - } - - // Remove from method get() generated character '"' and generate enum type - val typeOfDatabaseQuery = - DatabaseQueryType.valueOf(lastNode.get("type").toString().replace("\"", "")) - assertEquals( - DatabaseQueryType.DELETE, - typeOfDatabaseQuery, - "No deleting operation can be found" - ) - } - - // result.first().apply { - // var path = this.get("p") as Array<*> - // // the first node should be the label - // val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() - // assert(firstNode.labels().contains("PseudoIdentifier")) - // // the last node should be a DatabaseQuery - // val lastNode = (path.last() as InternalPath.SelfContainedSegment).end() - // assert(lastNode.labels().contains("DatabaseQuery")) - // // lastNode should be a deleting operation - // assertTrue(lastNode.get("isDeleting").isTrue, "No deleting operation can be - // found") - // } - } } From fa61bfb306d28e3cbd51cdac21996706c4232087 Mon Sep 17 00:00:00 2001 From: andreas Date: Fri, 24 Mar 2023 18:58:16 +0100 Subject: [PATCH 24/39] add: type property to ontology --- ...y_e4316a28-d966-4499-bd93-6be721055117.owx | 889 +++++++++--------- 1 file changed, 445 insertions(+), 444 deletions(-) diff --git a/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx b/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx index 2a9e00e..e1d0ec0 100644 --- a/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx +++ b/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx @@ -6,17 +6,11 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" ontologyIRI="urn:webprotege:ontology:e4316a28-d966-4499-bd93-6be721055117"> - - - - - - @@ -324,46 +318,46 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -417,109 +411,109 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -534,145 +528,145 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -706,42 +700,42 @@ - + - + - + - + - + - + xsd:java.util.List<de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration> @@ -756,14 +750,14 @@ - + - + @@ -793,7 +787,7 @@ - + @@ -815,14 +809,14 @@ - + - + @@ -852,28 +846,28 @@ - + - + - + xsd:java.util.Map<String, String> - + @@ -888,7 +882,7 @@ - + @@ -903,7 +897,7 @@ - + @@ -918,21 +912,21 @@ - + - + - + @@ -947,7 +941,7 @@ - + @@ -966,14 +960,14 @@ - + - + @@ -991,10 +985,17 @@ - + + + + + + + + @@ -1006,7 +1007,7 @@ - + @@ -1021,7 +1022,7 @@ - + @@ -1058,7 +1059,7 @@ - + @@ -1073,42 +1074,42 @@ - + - + - + - + - + - + xsd:de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration @@ -1119,7 +1120,7 @@ - + @@ -1133,7 +1134,7 @@ - + xsd:de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression @@ -1144,21 +1145,21 @@ - + - + - + @@ -1169,7 +1170,7 @@ - + @@ -1180,21 +1181,21 @@ - + - + - + @@ -1205,7 +1206,7 @@ - + @@ -1237,7 +1238,7 @@ - + @@ -1248,7 +1249,7 @@ - + @@ -1271,14 +1272,14 @@ - + - + @@ -1293,21 +1294,21 @@ - + - + - + @@ -1318,28 +1319,28 @@ - + - + - + - + @@ -1350,21 +1351,21 @@ - + - + xsd:de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression - + xsd:de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression @@ -1379,14 +1380,14 @@ - + - + @@ -1404,7 +1405,7 @@ - + @@ -1415,7 +1416,7 @@ - + @@ -1429,7 +1430,7 @@ - + @@ -1462,14 +1463,14 @@ - + - + @@ -1491,28 +1492,28 @@ - + - + - + xsd:java.util.ArrayList<String> - + xsd:java.util.ArrayList<Short> @@ -1535,7 +1536,7 @@ - + @@ -1557,21 +1558,21 @@ - + - + - + xsd:de.fraunhofer.aisec.cpg.graph.Node @@ -1582,7 +1583,7 @@ - + @@ -1597,7 +1598,7 @@ - + @@ -1612,7 +1613,7 @@ - + @@ -1641,14 +1642,14 @@ - + - + @@ -1659,7 +1660,7 @@ - + @@ -1678,7 +1679,7 @@ - + @@ -1696,7 +1697,7 @@ - + @@ -1707,7 +1708,7 @@ - + @@ -1718,28 +1719,28 @@ - + - + - + - + @@ -1754,35 +1755,35 @@ - + - + - + - + - + @@ -1801,7 +1802,7 @@ - + @@ -1815,419 +1816,419 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - + + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + listNamespacedPod - - + + ips += clusterIP ips += externalIP ips += loadBalancerIP name = metadata.name - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2947,492 +2948,492 @@ name = metadata.name - aws:Account + http://graph.clouditor.io/individuals/aws/Account AWS Account - aws:Aurora + http://graph.clouditor.io/individuals/aws/Aurora AWS Aurora - aws:CloudTrail + http://graph.clouditor.io/individuals/aws/CloudTrail AWS CloudTrail - aws:DynamoDB + http://graph.clouditor.io/individuals/aws/DynamoDB AWS DynamoDB - aws:EC2 + http://graph.clouditor.io/individuals/aws/EC2 AWS EC2 - aws:EC2Instance + http://graph.clouditor.io/individuals/aws/EC2Instance AWS EC2 Instance - aws:EC2LoadBalancer + http://graph.clouditor.io/individuals/aws/EC2LoadBalancer AWS EC2 LoadBalancer - aws:EC2NetworkInterface + http://graph.clouditor.io/individuals/aws/EC2NetworkInterface AWS EC2 Network Interface - aws:EC2Subnet + http://graph.clouditor.io/individuals/aws/EC2Subnet AWS EC2 Subnet - aws:EC2VPC + http://graph.clouditor.io/individuals/aws/EC2VPC AWS EC2 VPC - aws:IAM + http://graph.clouditor.io/individuals/aws/IAM AWS IAM - aws:IAMUser + http://graph.clouditor.io/individuals/aws/IAMUser AWS IAM User - aws:IoTCore + http://graph.clouditor.io/individuals/aws/IoTCore AWS IoT Core - aws:IoTDeviceManagement + http://graph.clouditor.io/individuals/aws/IoTDeviceManagement AWS IoT Device Management - aws:Lambda + http://graph.clouditor.io/individuals/aws/Lambda AWS Lambda - aws:RDS + http://graph.clouditor.io/individuals/aws/RDS AWS RDS - aws:S3 + http://graph.clouditor.io/individuals/aws/S3 AWS S3 - aws:S3Bucket + http://graph.clouditor.io/individuals/aws/S3Bucket AWS S3 Bucket - aws:S3Bucket + http://graph.clouditor.io/individuals/aws/S3Bucket AWS::S3::Bucket - aws:Volume + http://graph.clouditor.io/individuals/aws/Volume Volume - azure:ActivityLog + http://graph.clouditor.io/individuals/azure/ActivityLog Azure Activity Log - azure:Azure + http://graph.clouditor.io/individuals/azure/Azure Azure - azure:CosmosDB + http://graph.clouditor.io/individuals/azure/CosmosDB Azure CosmosDB - azure:CosmosDBAccount + http://graph.clouditor.io/individuals/azure/CosmosDBAccount Azure CosmosDB Account - azure:DeviceProvisioning + http://graph.clouditor.io/individuals/azure/DeviceProvisioning Azure Device Provisioning - azure:Disk + http://graph.clouditor.io/individuals/azure/Disk Azure Disk - azure:Functions + http://graph.clouditor.io/individuals/azure/Functions Azure Functions - azure:IoTHub + http://graph.clouditor.io/individuals/azure/IoTHub Azure IoT Hub - azure:SQLDB + http://graph.clouditor.io/individuals/azure/SQLDB Azure SQLDB - azure:SQLDBDatabase + http://graph.clouditor.io/individuals/azure/SQLDBDatabase Azure SQLDB Database - azure:StorageAccount + http://graph.clouditor.io/individuals/azure/StorageAccount Azure StorageAccount - azure:StorageAccount + http://graph.clouditor.io/individuals/azure/StorageAccount Microsoft.Storage/storageAccounts - azure:StorageAccounts + http://graph.clouditor.io/individuals/azure/StorageAccounts Azure StorageAccounts - azure:Subscription + http://graph.clouditor.io/individuals/azure/Subscription Azure Subscription - azure:VirtualMachines + http://graph.clouditor.io/individuals/azure/VirtualMachines Azure VirtualMachines - azure:VirtualMachinesVM + http://graph.clouditor.io/individuals/azure/VirtualMachinesVM Azure VirtualMachines VM - docker:Image + http://graph.clouditor.io/individuals/docker/Image Docker Image - k8s:Container + http://graph.clouditor.io/individuals/k8s/Container Kubernetes Container - k8s:Ingress + http://graph.clouditor.io/individuals/k8s/Ingress Kubernetes Ingress - k8s:Kubernetes + http://graph.clouditor.io/individuals/k8s/Kubernetes Kubernetes - k8s:Namespace + http://graph.clouditor.io/individuals/k8s/Namespace Kubernetes Namespace - k8s:Node + http://graph.clouditor.io/individuals/k8s/Node Kubernetes Node - k8s:Pod + http://graph.clouditor.io/individuals/k8s/Pod Kubernetes Pod - k8s:Service + http://graph.clouditor.io/individuals/k8s/Service Kubernetes Service - k8s:Volume + http://graph.clouditor.io/individuals/k8s/Volume Kubernetes Volume - library:Jersey + http://graph.clouditor.io/individuals/libraries/Jersey Jersey - library:SpringBoot + http://graph.clouditor.io/individuals/libraries/SpringBoot SpringBoot - library:SpringBootRESTController + http://graph.clouditor.io/individuals/libraries/SpringBootRESTController SpringBoot - REST Controller - library:SpringBootRequestMapping + http://graph.clouditor.io/individuals/libraries/SpringBootRequestMapping SpringBoot - Request Mapping - prop:activated + http://graph.clouditor.io/properties/activated activated - prop:algorithm + http://graph.clouditor.io/properties/algorithm algorithm - prop:apiListFunction + http://graph.clouditor.io/properties/apiListFunction apiListFunction - prop:argument + http://graph.clouditor.io/properties/argument argument - prop:backend + http://graph.clouditor.io/properties/backend backend - prop:call + http://graph.clouditor.io/properties/call call - prop:collectionOf + http://graph.clouditor.io/properties/collectionOf collectionOf - prop:deployedOn + http://graph.clouditor.io/properties/deployedOn deployedOn - prop:enabled + http://graph.clouditor.io/properties/enabled enabled - prop:enforced + http://graph.clouditor.io/properties/enforced enforced - prop:field + http://graph.clouditor.io/properties/field field - prop:from + http://graph.clouditor.io/properties/from from - prop:handler + http://graph.clouditor.io/properties/handler handler - prop:has + http://graph.clouditor.io/properties/has has - prop:hasMultiple + http://graph.clouditor.io/properties/hasMultiple hasMultiple - prop:impact + http://graph.clouditor.io/properties/impact impact - prop:implements + http://graph.clouditor.io/properties/implements implements - prop:inbound + http://graph.clouditor.io/properties/inbound inbound - prop:ips + http://graph.clouditor.io/properties/ips ips - prop:keyManagement + http://graph.clouditor.io/properties/keyManagement keymanagement - prop:keyManager + http://graph.clouditor.io/properties/keyManager keyManager - prop:keyUrl + http://graph.clouditor.io/properties/keyUrl keyUrl - prop:labels + http://graph.clouditor.io/properties/labels labels - prop:managementUrl + http://graph.clouditor.io/properties/managementUrl managementUrl - prop:method + http://graph.clouditor.io/properties/method method - prop:modify + http://graph.clouditor.io/properties/modify modify - prop:offers + http://graph.clouditor.io/properties/offers offers - prop:parent + http://graph.clouditor.io/properties/parent parent - prop:path + http://graph.clouditor.io/properties/path path - prop:policy + http://graph.clouditor.io/properties/policy policy - prop:ports + http://graph.clouditor.io/properties/ports ports - prop:programmingLanguage + http://graph.clouditor.io/properties/programmingLanguage programmingLanguage - prop:propertyMapping + http://graph.clouditor.io/properties/propertyMapping propertyMapping - prop:proxyTarget + http://graph.clouditor.io/properties/proxyTarget proxyTarget - prop:public + http://graph.clouditor.io/properties/public public - prop:region + http://graph.clouditor.io/properties/region region - prop:resourceOf + http://graph.clouditor.io/properties/resourceOf resourceOf - prop:restrictedPorts + http://graph.clouditor.io/properties/restrictedPorts restrictedPorts - prop:runsOn + http://graph.clouditor.io/properties/runsOn runsOn - prop:serves + http://graph.clouditor.io/properties/serves serves - prop:serviceOf + http://graph.clouditor.io/properties/serviceOf serviceOf - prop:source + http://graph.clouditor.io/properties/source source - prop:storage + http://graph.clouditor.io/properties/storage storage - prop:tlsVersion + http://graph.clouditor.io/properties/tlsVersion tlsVersion - prop:to + http://graph.clouditor.io/properties/to to - prop:translationUnits + http://graph.clouditor.io/properties/translationUnits translationUnits - prop:type + http://graph.clouditor.io/properties/type type - prop:url + http://graph.clouditor.io/properties/url url - prop:value + http://graph.clouditor.io/properties/value value From d5eb701f7ecb978024e741520ec96b077e070f85 Mon Sep 17 00:00:00 2001 From: andreas Date: Fri, 24 Mar 2023 18:59:53 +0100 Subject: [PATCH 25/39] feat: created performance test for evaluation of PPG extensions --- .../graph/GDPRExtensionPerformanceTest.kt | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt new file mode 100644 index 0000000..732ce0f --- /dev/null +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt @@ -0,0 +1,80 @@ +package io.clouditor.graph + +import java.util.concurrent.TimeUnit +import kotlin.io.path.* +import kotlinx.benchmark.Scope +import kotlinx.benchmark.readFile +import org.junit.Test +import org.openjdk.jmh.annotations.* +import kotlin.system.measureTimeMillis + +open class GDPRExtensionPerformanceTest { + + @Test + open fun testScalability() { + // create a list of times + val times = mutableListOf() + + // Warmup + executePPG( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" + ), + listOf(Path(".")) + ) + + // measure time needed for execution of PPG (20 times) + for (i in 1..20) { + // measure time + val timeForExecution = measureTimeMillis { + executePPG( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" + ), + listOf(Path(".")) + ) + } + println("Iteration $i: time for execution of PPG: $timeForExecution ms") + times.add(timeForExecution) + } + println("--------------------") + // calculate average time + var averageTime = 0L + for (time in times) { + averageTime += time + } + averageTime /= times.size + // print average time + println("Average time for execution of PPG: $averageTime ms") + + // calculate standard deviation + var standardDeviation = 0L + for (time in times) { + standardDeviation += (time - averageTime) * (time - averageTime) + } + standardDeviation /= times.size + standardDeviation = Math.sqrt(standardDeviation.toDouble()).toLong() + println("Standard deviation for execution of PPG: $standardDeviation ms") + + // calculate maximum time + var maximumTime = 0L + for (time in times) { + if (time > maximumTime) { + maximumTime = time + } + } + println("Maximum time for execution of PPG: $maximumTime ms") + + // calculate minimum time + var minimumTime = Long.MAX_VALUE + for (time in times) { + if (time < minimumTime) { + minimumTime = time + } + } + println("Minimum time for execution of PPG: $minimumTime ms") + } + +} \ No newline at end of file From 65d6f2b3969bb78eee5380e31b486c494ec836f2 Mon Sep 17 00:00:00 2001 From: andreas Date: Fri, 24 Mar 2023 19:00:30 +0100 Subject: [PATCH 26/39] refactor: updated data type of type property according to ontology --- .../io/clouditor/graph/passes/DatabaseOperationPass.kt | 5 ++--- .../java/io/clouditor/graph/passes/GormDatabasePass.kt | 6 +++--- .../java/io/clouditor/graph/passes/python/Psycopg2Pass.kt | 2 +- .../java/io/clouditor/graph/passes/python/PyMongoPass.kt | 8 ++++---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt index d7fe00a..23c5a00 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/DatabaseOperationPass.kt @@ -7,7 +7,6 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.passes.Pass import io.clouditor.graph.* -import io.clouditor.graph.utils.DatabaseQueryType abstract class DatabaseOperationPass : Pass() { @@ -43,9 +42,9 @@ abstract class DatabaseOperationPass : Pass() { storage: List, calls: List, app: Application?, - type: DatabaseQueryType + type: String ): DatabaseQuery { - val op = DatabaseQuery(modify, calls, storage, connect.to, type) + val op = DatabaseQuery(modify, type, calls, storage, connect.to) op.location = app?.location storage.forEach { diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt index f877ccc..8d78547 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/GormDatabasePass.kt @@ -125,7 +125,7 @@ class GormDatabasePass : DatabaseOperationPass() { mutableListOf(), calls, app, - DatabaseQueryType.READ + DatabaseQueryType.READ.toString() ) op.name = call.name @@ -155,7 +155,7 @@ class GormDatabasePass : DatabaseOperationPass() { mutableListOf(), mutableListOf(call), app, - DatabaseQueryType.CREATE + DatabaseQueryType.CREATE.toString() ) op.name = call.name @@ -177,7 +177,7 @@ class GormDatabasePass : DatabaseOperationPass() { mutableListOf(), mutableListOf(call), app, - DatabaseQueryType.UPDATE + DatabaseQueryType.UPDATE.toString() ) op.name = call.name diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt index 569133a..cc5453e 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/Psycopg2Pass.kt @@ -157,7 +157,7 @@ class Psycopg2Pass : DatabaseOperationPass() { storage, mutableListOf(call), app, - DatabaseQueryType.UNKNOWN + DatabaseQueryType.UNKNOWN.toString() ) op.name = call.name diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt index e76bdb2..7b1de07 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PyMongoPass.kt @@ -177,7 +177,7 @@ class PyMongoPass : DatabaseOperationPass() { storage, listOf(mce), app, - DatabaseQueryType.CREATE + DatabaseQueryType.CREATE.toString() ) // data flows from first argument to op @@ -193,7 +193,7 @@ class PyMongoPass : DatabaseOperationPass() { storage, listOf(mce), app, - DatabaseQueryType.READ + DatabaseQueryType.READ.toString() ) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) @@ -211,7 +211,7 @@ class PyMongoPass : DatabaseOperationPass() { storage, listOf(mce), app, - DatabaseQueryType.DELETE + DatabaseQueryType.DELETE.toString() ) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) @@ -229,7 +229,7 @@ class PyMongoPass : DatabaseOperationPass() { storage, listOf(mce), app, - DatabaseQueryType.UPDATE + DatabaseQueryType.UPDATE.toString() ) // data flows from first argument to op mce.arguments.firstOrNull()?.addNextDFG(op) From ff07537bb993d1303ff510cdc284485cb61485c3 Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 26 Mar 2023 15:30:53 +0200 Subject: [PATCH 27/39] add: small testcase adjustements for Art 16 and Art 17 --- .../RightToErasure/Python/client.py | 15 ++++++++++----- .../RightToErasure/Python/server.py | 17 +++++++++++++++++ .../RightToErasure/Python_validation/client.py | 8 ++++---- .../RightToErasure/Python_validation/server.py | 2 +- .../RightToRectification/Python/client.py | 18 ++++++++++++------ .../RightToRectification/Python/server.py | 15 +++++++++++++++ .../Python_validation/client.py | 17 ++++++++++++----- .../Python_validation/server.py | 15 +++++++++++++++ 8 files changed, 86 insertions(+), 21 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py index 2a1578f..9480f74 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py @@ -2,14 +2,19 @@ import requests -def delete_own_data(): +def delete_own_data(personal_data): url = 'test-online-notepad.com/data' + requests.delete(url, json = personal_data) + +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.put(url, json = personal_data) + +if __name__ == '__main__': #@PseudoIdentifier personal_data = { "username": "testuser", "notes": ["note1", "note2", "note3"] } - requests.delete(url, json = personal_data) - -if __name__ == '__main__': - delete_own_data() + store_personal_data_on_server(personal_data) + delete_own_data(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py index fa0d97f..c06457c 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py @@ -25,5 +25,22 @@ def parse_data(): requests.delete("ext-ad-server.com/data", json = data) return "Created", 201 +@app.route("/store_data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + # send data to external advertising server + url = 'ext-ad-server.com/data' + requests.put(url, json = data) + return "OK", 200 + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py index f32dcd9..06cae19 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py @@ -6,9 +6,9 @@ def delete_own_data(personal_data): url = 'test-online-notepad.com/data' requests.delete(url, json = personal_data) -def send_data_to_external_advertising_server(personal_data): - url = 'test-online-notepad.com/data' - requests.post(url, json = personal_data) +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.put(url, json = personal_data) if __name__ == '__main__': @@ -17,6 +17,6 @@ def send_data_to_external_advertising_server(personal_data): "username": "testuser", "notes": ["note1", "note2", "note3"] } - send_data_to_external_advertising_server(personal_data) + store_personal_data_on_server(personal_data) delete_own_data(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py index bb03e7e..f4656f9 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py @@ -23,7 +23,7 @@ def parse_data(): # VALIDATION: no external advertising server is informed about the deletion and no deletion is performed return "OK", 200 -@app.route("/data", methods=['PUT']) +@app.route("/store_data", methods=['PUT']) def parse_data(): req = request.json data = { diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py index b58a773..3e2b79c 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py @@ -2,16 +2,22 @@ import requests -def rectify(changed_name): +def rectify(personal_data, changed_name): url = 'test-online-notepad.com/data' + + personal_data["name"] = changed_name + requests.put(url, json = personal_data) + +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.put(url, json = personal_data) + +if __name__ == '__main__': #@PseudoIdentifier personal_data = { "username": "testuser", "name": "firstname lastname", "notes": ["note1", "note2", "note3"] } - personal_data["name"] = changed_name - requests.put(url, json = personal_data) - -if __name__ == '__main__': - rectify("New Name") + store_personal_data_on_server(personal_data) + rectify(personal_data, "New Name") diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py index 6dcfd3f..81a8b07 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py @@ -23,5 +23,20 @@ def parse_data(): user_db_collection.update_one({"name": data['name']}) return "Created", 201 +@app.route("/store_data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "name": req['name'], + "notes": req['notes'] + } + if user_db_collection.find( { "name": data['name'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + return "OK", 200 + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py index b58a773..23e959a 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py @@ -2,16 +2,23 @@ import requests -def rectify(changed_name): +def rectify(personal_data, changed_name): url = 'test-online-notepad.com/data' + + personal_data["name"] = changed_name + requests.put(url, json = personal_data) + +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.put(url, json = personal_data) + +if __name__ == '__main__': #@PseudoIdentifier personal_data = { "username": "testuser", "name": "firstname lastname", "notes": ["note1", "note2", "note3"] } - personal_data["name"] = changed_name - requests.put(url, json = personal_data) + store_personal_data_on_server(personal_data) + rectify(personal_data, "New Name") -if __name__ == '__main__': - rectify("New Name") diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py index c3f2d2f..69e4ec3 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py @@ -23,5 +23,20 @@ def parse_data(): # VALIDATION: no rectification is performed => No update call to the database return "Created", 201 +@app.route("/store_data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "name": req['name'], + "notes": req['notes'] + } + if user_db_collection.find( { "name": data['name'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + return "OK", 200 + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From fa6accbd742aedca0b0b08bdce181bdbd75f9825 Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 26 Mar 2023 18:56:42 +0200 Subject: [PATCH 28/39] add: created testcase descriptions for test suite --- .../NotificationObligation/Python/README.md | 5 ++++- .../NotificationObligation/Python_validation/README.md | 6 +++++- .../RightToDataPortability/Python/README.md | 5 ++++- .../RightToDataPortability/Python_validation/README.md | 5 ++++- .../GDPRComplianceChecks/RightToErasure/Python/README.md | 5 ++++- .../RightToErasure/Python_validation/README.md | 5 ++++- .../RightToRectification/Python/README.md | 5 ++++- .../RightToRectification/Python_validation/README.md | 5 ++++- 8 files changed, 33 insertions(+), 8 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md index d9f25b6..756b13e 100644 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/README.md @@ -1 +1,4 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 19 - Notification Obligation +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database and communicates parts of the personal data to a third party (external advertising server). The client offers a function for the deletion and rectification of his personal data. The server peforms these requests and notifies the external advertising server about the deletion and rectification of the personal data. The client also offers a function to retrieve information about the data recipients. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 19. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md index d9f25b6..a87d64e 100644 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/README.md @@ -1 +1,5 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 19 Validation - Notification Obligation +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database and communicates parts of the personal data to a third party (external advertising server). The client offers a function for the deletion and rectification of his personal data. The server peforms these requests and does not notify the external advertising server about the deletion and rectification of the personal data. The client also offers a function to retrieve information about the data recipients but does not hand out information to the user. +- Expected outcome: + - The server is not informing the external advertising server about the deletion and rectification of the personal data is detected. + - The client does not hand out information about the data recipients to the user is detected. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md index d9f25b6..b152d2b 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/README.md @@ -1 +1,4 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 20 - Right to Data Portability +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database. The client offers a function for the retrieval of his personal data in a machine-readable format. Also a function for the transfer of the personal data to another data controller is integrated in the client. The server performs these requests and transfers the personal data to the client or to another data controller in a machine-readable format. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 20. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md index d9f25b6..8baafcd 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/README.md @@ -1 +1,4 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 20 Validation - Right to Data Portability +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database. The client offers a function for the retrieval of his personal data in a machine-readable format, but does not store the personal data. Also a function for the transfer of the personal data to another data controller is integrated in the client. The server does not transfer the personal data to the client or to another data controller in a machine-readable format. +- Expected outcome: + - The server does not transfer the personal data to the client or to another data controller in a machine-readable format is detected. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md index d9f25b6..eff557f 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/README.md @@ -1 +1,4 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 17 - Right to Erasure +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database and sends it to third parties. The client offers a function for the deletion of his personal data. The server performs this request, deletes the personal data and informs other data recipients about the deletion request. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 17. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md index d9f25b6..46a35c4 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/README.md @@ -1 +1,4 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 17 Validation - Right to Erasure +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database and communicates it to third parties. The client offers a function for the deletion of his personal data. The server does not delete the personal data and does not inform other data recipients about the deletion request. +- Expected outcome: + - The server does not delete the personal data and does not inform other data recipients about the deletion request is detected. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md index d9f25b6..2be3513 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/README.md @@ -1 +1,4 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 16 - Right to Rectification +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database. The client offers a function for the rectification of his personal data. The server performs this request and rectifies the personal data and stores the updated data in the database. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 16. diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md index d9f25b6..3a34420 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/README.md @@ -1 +1,4 @@ -TODO: Description \ No newline at end of file +# Test Case: Article 16 Validation - Right to Rectification +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database. The client offers a function for the rectification of his personal data. The server does not perform rectification of the personal data and therefore does not update the personal data record in the database. +- Expected outcome: + - The non-rectification of the personal data (the update call to the database of the personal data) is detected. From 8423f1e2e134e9431e7e844252fb932bdd78b8c0 Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 26 Mar 2023 19:23:06 +0200 Subject: [PATCH 29/39] refactor: reworked testcase art 16 --- .../RightToRectification/Python/client.py | 6 +++--- .../RightToRectification/Python_validation/client.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py index 3e2b79c..e7570b7 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py @@ -2,10 +2,10 @@ import requests -def rectify(personal_data, changed_name): +def rectify(personal_data): url = 'test-online-notepad.com/data' - personal_data["name"] = changed_name + personal_data["name"] = "new name" requests.put(url, json = personal_data) def store_personal_data_on_server(personal_data): @@ -20,4 +20,4 @@ def store_personal_data_on_server(personal_data): "notes": ["note1", "note2", "note3"] } store_personal_data_on_server(personal_data) - rectify(personal_data, "New Name") + rectify(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py index 23e959a..ab612ee 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py @@ -2,10 +2,10 @@ import requests -def rectify(personal_data, changed_name): +def rectify(personal_data): url = 'test-online-notepad.com/data' - personal_data["name"] = changed_name + personal_data["name"] = "new name" requests.put(url, json = personal_data) def store_personal_data_on_server(personal_data): @@ -20,5 +20,5 @@ def store_personal_data_on_server(personal_data): "notes": ["note1", "note2", "note3"] } store_personal_data_on_server(personal_data) - rectify(personal_data, "New Name") + rectify(personal_data) From 5482d817d824c8009e7c923ecb32f2cc46af709d Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 26 Mar 2023 19:23:23 +0200 Subject: [PATCH 30/39] add: performance optimization of queries --- .../io/clouditor/graph/GDPRComplianceChecks.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 04bef24..ac5e02d 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -20,7 +20,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" ) // create a list for all pseudoidentifiers with no update call connected to them via a data @@ -51,7 +51,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" ) // create a list for all pseudoidentifiers with no update call connected to them via a data @@ -82,7 +82,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" ) // create a list for all pseudoidentifiers with no delete call connected to them via a data // flow @@ -112,7 +112,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]-(he1:HttpEndpoint)--()-[:DFG*]-(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" ) // create a list for all pseudoidentifiers with no delete call connected to them via a data // flow @@ -142,7 +142,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" ), listOf(Path(".")), - "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(hr1) WHERE (hr1.name='DELETE') } RETURN path1" + "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr2:HttpRequest {name: 'DELETE'})-[:TO]-(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(hr3:HttpRequest) WHERE (hr3.name='DELETE') } RETURN path1" ) // create a list for all pseudoidentifiers, which are communicated to extern with no delete // call to extern connected to them via a data flow @@ -175,7 +175,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation" ), listOf(Path(".")), - "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1) WHERE NOT (hr1)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]-(hr1) WHERE (hr1.name='DELETE') } RETURN path1" + "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr2:HttpRequest {name: 'DELETE'})-[:TO]-(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(hr3:HttpRequest) WHERE (hr3.name='DELETE') } RETURN path1" ) // create a list for all pseudoidentifiers, which are communicated to extern with no delete // call to extern connected to them via a data flow @@ -208,7 +208,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" ) // create a list for all pseudoidentifiers, which are communicated to extern with no delete // or update call to extern connected to them via a data flow @@ -276,7 +276,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]-(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]-(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" ) // create a list for all pseudoidentifiers, which are communicated to extern with no delete // or update call to extern connected to them via a data flow From 2aec9c043e6660b9411efc04b41a2cb73e72bab7 Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 26 Mar 2023 20:22:50 +0200 Subject: [PATCH 31/39] add: updated query of Art.19 for better detection of urls --- .../src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index ac5e02d..2061c10 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -240,7 +240,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" ), listOf(Path(".")), - "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name CONTAINS \".com\") AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" + "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name =~ '.*\\\\b.(com|org|net|edu|gov|biz|info|io|tv|me|co|us|ca|uk|au|in|de)\\\\b.*') AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" ) // create a list for all personal data recipients, which are not mentioned in the // information about the data recipients @@ -308,7 +308,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation" ), listOf(Path(".")), - "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name CONTAINS \".com\") AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" + "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name =~ '.*\\\\b.(com|org|net|edu|gov|biz|info|io|tv|me|co|us|ca|uk|au|in|de)\\\\b.*') AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" ) // create a list for all personal data recipients, which are not mentioned in the // information about the data recipients From 5b41ec4cb83102b7206acb3d295fc0bf894aac5a Mon Sep 17 00:00:00 2001 From: andreas Date: Fri, 31 Mar 2023 10:28:32 +0200 Subject: [PATCH 32/39] fix: adjusted configs and added missing mongo_host attribute --- .../NotificationObligation/Python/config.yml | 6 +++++- .../NotificationObligation/Python/server.py | 1 + .../NotificationObligation/Python_validation/config.yml | 6 +++++- .../NotificationObligation/Python_validation/server.py | 1 + .../RightToDataPortability/Python/config.yml | 4 +--- .../RightToDataPortability/Python/server.py | 1 + .../RightToDataPortability/Python_validation/config.yml | 4 +--- .../RightToDataPortability/Python_validation/server.py | 1 + .../RightToErasure/Python_validation/config.yml | 6 +++++- .../RightToErasure/Python_validation/server.py | 1 + .../RightToRectification/Python/config.yml | 4 +--- .../RightToRectification/Python/server.py | 2 +- .../RightToRectification/Python_validation/config.yml | 4 +--- .../RightToRectification/Python_validation/server.py | 1 + 14 files changed, 26 insertions(+), 16 deletions(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml index bd17c02..7180571 100644 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/config.yml @@ -6,4 +6,8 @@ services: name: client - type: external-advertising-server name: external-advertising-server - host: ext-ad-server.com \ No newline at end of file + host: ext-ad-server.com + - type: db + name: mongo + storages: + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py index a57a449..27b78b1 100755 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py @@ -4,6 +4,7 @@ from pymongo import MongoClient, database import requests +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml index bd17c02..7180571 100644 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/config.yml @@ -6,4 +6,8 @@ services: name: client - type: external-advertising-server name: external-advertising-server - host: ext-ad-server.com \ No newline at end of file + host: ext-ad-server.com + - type: db + name: mongo + storages: + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py index 6689fbb..9ba979e 100755 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation/server.py @@ -4,6 +4,7 @@ from pymongo import MongoClient, database import requests +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml index cb48069..bdc9375 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/config.yml @@ -10,8 +10,6 @@ services: - userdata - otherdata - type: db - directory: server name: mongo storages: - - userdata - - otherdata \ No newline at end of file + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py index f98e54d..7273023 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py @@ -4,6 +4,7 @@ from pymongo import MongoClient, database import requests +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml index cb48069..bdc9375 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/config.yml @@ -10,8 +10,6 @@ services: - userdata - otherdata - type: db - directory: server name: mongo storages: - - userdata - - otherdata \ No newline at end of file + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py index 8e6f193..de752ba 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py @@ -4,6 +4,7 @@ from pymongo import MongoClient, database import requests +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml index 1514310..30685fc 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/config.yml @@ -3,4 +3,8 @@ services: name: server host: test-online-notepad.com - type: client - name: client \ No newline at end of file + name: client + - type: db + name: mongo + storages: + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py index f4656f9..46613e4 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py @@ -4,6 +4,7 @@ from pymongo import MongoClient, database import requests +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml index cb48069..bdc9375 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/config.yml @@ -10,8 +10,6 @@ services: - userdata - otherdata - type: db - directory: server name: mongo storages: - - userdata - - otherdata \ No newline at end of file + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py index 81a8b07..cf3e771 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py @@ -2,7 +2,7 @@ from flask import Flask, request from pymongo import MongoClient, database - +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml index cb48069..bdc9375 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/config.yml @@ -10,8 +10,6 @@ services: - userdata - otherdata - type: db - directory: server name: mongo storages: - - userdata - - otherdata \ No newline at end of file + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py index 69e4ec3..a78946a 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py @@ -3,6 +3,7 @@ from flask import Flask, request from pymongo import MongoClient, database +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records From b28ec8da819f6dec787698950bcc92287c0243bf Mon Sep 17 00:00:00 2001 From: andreas Date: Tue, 4 Apr 2023 19:35:12 +0200 Subject: [PATCH 33/39] add: adjusted testcases and added url prop to ontology --- .../java/io/clouditor/graph/passes/HttpClientPass.kt | 2 +- ...e_ontology_e4316a28-d966-4499-bd93-6be721055117.owx | 7 +++++++ .../NotificationObligation/Python/client.py | 10 ++++++---- .../NotificationObligation/Python/server.py | 8 +++++++- .../RightToErasure/Python/config.yml | 7 ++++++- .../RightToErasure/Python/server.py | 1 + 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/HttpClientPass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/HttpClientPass.kt index 4b9a016..6c7e5b0 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/HttpClientPass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/HttpClientPass.kt @@ -16,7 +16,7 @@ abstract class HttpClientPass : Pass() { app: Application? ): HttpRequest { val endpoints = getEndpointsForUrl(t, url, method) - val request = HttpRequest(call, body, endpoints) + val request = HttpRequest(call, body, endpoints, url) request.name = method request.location = call.location diff --git a/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx b/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx index e1d0ec0..1f5e8d0 100644 --- a/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx +++ b/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx @@ -1124,6 +1124,13 @@ + + + + + + + diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py index 0c456cd..2130a1b 100755 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/client.py @@ -10,11 +10,12 @@ def rectify_data(personal_data): url = 'test-online-notepad.com/data' requests.put(url, json = personal_data) -def get_information_about_data_recipients(): +def get_information_about_data_recipients(personal_data): data_recipients_information = "receiver of your personal data: ext-ad-server.com/data (external advertising server)\nIt is used for the following purposes: advertising" # create file containing the information + data_recipients_server = requests.get("test-online-notepad.com/data_recipients", params = {"auth_token": personal_data["auth_token"]}) f = open("data_recipients_information.txt", "w") - f.write(data_recipients_information) + f.write(data_recipients_server) f.close() def send_data_to_server(personal_data): @@ -25,9 +26,10 @@ def send_data_to_server(personal_data): #@PseudoIdentifier personal_data = { "username": "testuser", - "notes": ["note1", "note2", "note3"] + "notes": ["note1", "note2", "note3"], + "auth_token": "1234567890" } send_data_to_server(personal_data) rectify_data(personal_data) - get_information_about_data_recipients() + get_information_about_data_recipients(personal_data) delete_data(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py index 27b78b1..5ab712a 100755 --- a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python/server.py @@ -11,6 +11,8 @@ app = Flask(__name__) +data_recipients_information = "receiver of your personal data: ext-ad-server.com/data (external advertising server)\nIt is used for the following purposes: advertising" + @app.route("/data", methods=['DELETE']) def parse_data(): req = request.json @@ -21,7 +23,7 @@ def parse_data(): user_db_collection.delete_one({"username": data['username']}) # inform external advertising server about the deletion - requests.delete("ext-ad-server.com/data", json = data) + requests.delete("ext1-ad-server.com/data", json = data) return "OK", 200 @app.route("/data", methods=['PUT']) @@ -49,5 +51,9 @@ def parse_data(): requests.post("ext-ad-server.com/data", json = data['notes']) return "OK", 200 +@app.route("/data_recipients", methods=['GET']) +def parse_data(): + return data_recipients_information, 200 + if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml index 1514310..8ddc973 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/config.yml @@ -3,4 +3,9 @@ services: name: server host: test-online-notepad.com - type: client - name: client \ No newline at end of file + name: client + - type: db + name: mongo + storages: + - userdata + - otherdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py index c06457c..2686510 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py @@ -4,6 +4,7 @@ from pymongo import MongoClient, database import requests +mongo_host = "mongo" user_db_client = MongoClient("mongodb://mongo:27017/") user_db = user_db_client.userdata user_db_collection = user_db.records From 39ed8bf75fbf3b269de6eac9e075da48bd02543d Mon Sep 17 00:00:00 2001 From: andreas Date: Sat, 8 Apr 2023 20:51:58 +0200 Subject: [PATCH 34/39] feat: create FileWritePass + PythonFileWritePass + GoFileWritePass and updated ontology --- .../clouditor/graph/passes/FileWritePass.kt | 22 ++++++++++++ .../graph/passes/golang/GoFileWritePass.kt | 35 +++++++++++++++++++ .../passes/python/PythonFileWritePass.kt | 34 ++++++++++++++++++ ...y_e4316a28-d966-4499-bd93-6be721055117.owx | 19 ++++++++++ 4 files changed, 110 insertions(+) create mode 100644 cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt create mode 100644 cloudpg/src/main/java/io/clouditor/graph/passes/golang/GoFileWritePass.kt create mode 100644 cloudpg/src/main/java/io/clouditor/graph/passes/python/PythonFileWritePass.kt diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt new file mode 100644 index 0000000..4633d15 --- /dev/null +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt @@ -0,0 +1,22 @@ +package io.clouditor.graph.passes + +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression +import de.fraunhofer.aisec.cpg.passes.Pass +import io.clouditor.graph.Application +import io.clouditor.graph.FileWrite + +abstract class FileWritePass: Pass() { + + protected fun createFileWrite( + t: TranslationResult, + call: CallExpression, + app: Application? + ): FileWrite { + // Create node + val fileWriteNode = FileWrite(call) + // Add to functionalities if necessary + app?.functionalities?.plusAssign(fileWriteNode) + return fileWriteNode + } +} \ No newline at end of file diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/golang/GoFileWritePass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/golang/GoFileWritePass.kt new file mode 100644 index 0000000..d410f09 --- /dev/null +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/golang/GoFileWritePass.kt @@ -0,0 +1,35 @@ +package io.clouditor.graph.passes.python + +import de.fraunhofer.aisec.cpg.ExperimentalPython +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import io.clouditor.graph.findApplicationByTU +import io.clouditor.graph.passes.FileWritePass + +@ExperimentalPython +class GoFileWritePass: FileWritePass() { + override fun accept(t: TranslationResult) { + for (tu in t.translationUnits) { + tu.accept( + Strategy::AST_FORWARD, + object : IVisitor() { + // check all MemberCallExpressions + fun visit(r: MemberCallExpression) { + // look for writeFile() call of os library + if (r.name == "WriteFile" && r.base.name == "os") { + createFileWrite(t, r, t.findApplicationByTU(tu)) + } + } + } + ) + } + } + + override fun cleanup() { + // Nothing to do + } + +} \ No newline at end of file diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/python/PythonFileWritePass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PythonFileWritePass.kt new file mode 100644 index 0000000..1dfdee9 --- /dev/null +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/python/PythonFileWritePass.kt @@ -0,0 +1,34 @@ +package io.clouditor.graph.passes.python + +import de.fraunhofer.aisec.cpg.ExperimentalPython +import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.statements.expressions.MemberCallExpression +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import io.clouditor.graph.findApplicationByTU +import io.clouditor.graph.passes.FileWritePass + +@ExperimentalPython +class PythonFileWritePass: FileWritePass() { + override fun accept(t: TranslationResult) { + for (tu in t.translationUnits) { + tu.accept( + Strategy::AST_FORWARD, + object : IVisitor() { + fun visit(r: MemberCallExpression) { + // look for write() call + if (r.name == "write") { + createFileWrite(t, r, t.findApplicationByTU(tu)) + } + } + } + ) + } + } + + override fun cleanup() { + // Nothing to do + } + +} \ No newline at end of file diff --git a/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx b/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx index 1f5e8d0..b2d84f0 100644 --- a/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx +++ b/owl2java/resources/urn_webprotege_ontology_e4316a28-d966-4499-bd93-6be721055117.owx @@ -305,6 +305,9 @@ + + + @@ -1817,6 +1820,17 @@ + + + + + + + + + xsd:de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression + + @@ -3453,6 +3467,11 @@ name = metadata.name xsd:de.fraunhofer.aisec.cpg.graph.Node xsd:de.fraunhofer.aisec.cpg.graph.Node + + + #FileCreate + FileWrite + From 28d33f30dbb92bac02ec69444801bc15f2fb4c85 Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 9 Apr 2023 12:13:20 +0200 Subject: [PATCH 35/39] refactor: updated queries according to updated ontology. registered PythonFileWritePass at App.kt --- .../src/main/java/io/clouditor/graph/App.kt | 1 + .../clouditor/graph/GDPRComplianceChecks.kt | 96 ++++++------------- .../RightToDataPortability/Python/client.py | 2 +- .../RightToDataPortability/Python/server.py | 2 +- .../Python_validation/client.py | 2 +- .../Python_validation/server.py | 2 +- .../RightToErasure/Python/client.py | 2 +- .../RightToErasure/Python/server.py | 2 +- .../Python_validation/client.py | 2 +- .../Python_validation/server.py | 2 +- .../RightToRectification/Python/client.py | 2 +- .../RightToRectification/Python/server.py | 2 +- .../Python_validation/client.py | 2 +- .../Python_validation/server.py | 2 +- 14 files changed, 44 insertions(+), 77 deletions(-) diff --git a/cloudpg/src/main/java/io/clouditor/graph/App.kt b/cloudpg/src/main/java/io/clouditor/graph/App.kt index 658ab9f..6dcb660 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/App.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/App.kt @@ -174,6 +174,7 @@ object App : Callable { .registerPass(GormDatabasePass()) .registerPass(PyMongoPass()) .registerPass(Psycopg2Pass()) + .registerPass(PythonFileWritePass()) .processAnnotations(true) if (labelsEnabled) { diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index 2061c10..d55cf46 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -7,6 +7,8 @@ import kotlin.test.assertNotEquals import org.junit.Test import org.junit.jupiter.api.Tag import org.neo4j.driver.internal.InternalPath +import kotlin.test.assertFalse +import kotlin.test.assertTrue @Tag("TestingLibrary") open class GDPRComplianceChecks { @@ -20,11 +22,10 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: 'POST'})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='UPDATE') AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) - // create a list for all pseudoidentifiers with no update call connected to them via a data - // flow + // create a list for all pseudoidentifiers with no update call connected to them via a data flow val listOfAllPseudoIdentifierWithNoUpdateByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { @@ -51,7 +52,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='UPDATE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: 'POST'})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='UPDATE') AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no update call connected to them via a data @@ -82,10 +83,9 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: 'POST'})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='DELETE') AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) - // create a list for all pseudoidentifiers with no delete call connected to them via a data - // flow + // create a list for all pseudoidentifiers with no delete call connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { @@ -112,10 +112,9 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='DELETE') } RETURN path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: 'POST'})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='DELETE') AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) - // create a list for all pseudoidentifiers with no delete call connected to them via a data - // flow + // create a list for all pseudoidentifiers with no delete call connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { @@ -142,10 +141,9 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python" ), listOf(Path(".")), - "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr2:HttpRequest {name: 'DELETE'})-[:TO]-(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(hr3:HttpRequest) WHERE (hr3.name='DELETE') } RETURN path1" + "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr2:HttpRequest {name: 'DELETE'})-[:TO]-(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(hr3:HttpRequest) WHERE (hr3.name='DELETE') AND (hr3.url = hr1.url) AND NOT (hr3)-[:TO]-(:HttpEndpoint) } RETURN path1" ) - // create a list for all pseudoidentifiers, which are communicated to extern with no delete - // call to extern connected to them via a data flow + // create a list for all pseudoidentifiers, which are communicated to extern with no delete call to extern connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { @@ -175,10 +173,9 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation" ), listOf(Path(".")), - "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr2:HttpRequest {name: 'DELETE'})-[:TO]-(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(hr3:HttpRequest) WHERE (hr3.name='DELETE') } RETURN path1" + "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr2:HttpRequest {name: 'DELETE'})-[:TO]-(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(hr3:HttpRequest) WHERE (hr3.name='DELETE') AND (hr3.url = hr1.url) AND NOT (hr3)-[:TO]-(:HttpEndpoint) } RETURN path1" ) - // create a list for all pseudoidentifiers, which are communicated to extern with no delete - // call to extern connected to them via a data flow + // create a list for all pseudoidentifiers, which are communicated to extern with no delete call to extern connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity = mutableListOf() // iterate over all paths and add to the list result.forEach { @@ -208,10 +205,9 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND ((hr4.name='DELETE') OR (hr4.name='PUT')) AND (hr4.url = hr2.url) } RETURN path1" ) - // create a list for all pseudoidentifiers, which are communicated to extern with no delete - // or update call to extern connected to them via a data flow + // create a list for all pseudoidentifiers, which are communicated to extern with no delete or update call to extern connected to them via a data flow val listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity = mutableListOf() // iterate over all paths and add to the list result_data_flows.forEach { @@ -240,31 +236,16 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" ), listOf(Path(".")), - "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name =~ '.*\\\\b.(com|org|net|edu|gov|biz|info|io|tv|me|co|us|ca|uk|au|in|de)\\\\b.*') AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT hr2.url) as externalDataRecipients MATCH path2=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->()<-[:DFG*]-(l2:Literal) WHERE ALL(recipient IN externalDataRecipients WHERE l2.value CONTAINS recipient) RETURN path2" ) - // create a list for all personal data recipients, which are not mentioned in the - // information about the data recipients - val listOfAllPersonalDataRecipientsNotMentionedInInformation = mutableListOf() - // iterate over all paths and add to the list + // iterate over all found paths and check if the first node is a FileWrite result_data_storage.forEach { - var path = it.get("p3") as Array<*> - + val path = it.get("path2") as Array<*> // the first node is the literal, which contains the name of the personal data recipient val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() - if (firstNode.labels().contains("Literal")) { - val nameOfPersonalDataRecipient = firstNode.get("name").toString() - // add the literal to the list if it is not already in it - if (!listOfAllPersonalDataRecipientsNotMentionedInInformation.contains( - nameOfPersonalDataRecipient - ) - ) - listOfAllPersonalDataRecipientsNotMentionedInInformation.add( - nameOfPersonalDataRecipient - ) - } + // if the first node is a FileWrite => A call expression that writes a literal containing information of the data recipients could be found => the code is compliant to article 19 + assertTrue(firstNode.labels().contains("FileWrite")) } - // if the code is compliant to article 19, the list should be empty aswell - assertEquals(0, listOfAllPersonalDataRecipientsNotMentionedInInformation.size) } @Test @@ -276,7 +257,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation" ), listOf(Path(".")), - "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND (hr4.name='DELETE') OR (hr4.name='PUT')} return path1" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND ((hr4.name='DELETE') OR (hr4.name='PUT')) AND (hr4.url = hr2.url) } RETURN path1" ) // create a list for all pseudoidentifiers, which are communicated to extern with no delete // or update call to extern connected to them via a data flow @@ -308,31 +289,16 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_validation" ), listOf(Path(".")), - "MATCH p1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest)-[:CALL]-()-[:ARGUMENTS]-()--(l1:Literal) WHERE (l1.name =~ '.*\\\\b.(com|org|net|edu|gov|biz|info|io|tv|me|co|us|ca|uk|au|in|de)\\\\b.*') AND NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT l1) as dataRecipients, l1 MATCH p2=(m:MemberCallExpression {name:\"write\"})-[:ARGUMENTS]-()--(l2:Literal), p3=(l1)--() WHERE ANY(recipient IN dataRecipients WHERE NOT l2.value CONTAINS recipient.name) RETURN p3" + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT hr2.url) as externalDataRecipients MATCH path2=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->()<-[:DFG*]-(l2:Literal) WHERE ALL(recipient IN externalDataRecipients WHERE l2.value CONTAINS recipient) RETURN path2" ) - // create a list for all personal data recipients, which are not mentioned in the - // information about the data recipients - val listOfAllPersonalDataRecipientsNotMentionedInInformation = mutableListOf() - // iterate over all paths and add to the list + // iterate over all found paths and check if the first node is a FileWrite result_data_storage.forEach { - var path = it.get("p3") as Array<*> - + val path = it.get("path2") as Array<*> // the first node is the literal, which contains the name of the personal data recipient val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() - if (firstNode.labels().contains("Literal")) { - val nameOfPersonalDataRecipient = firstNode.get("name").toString() - // add the literal to the list if it is not already in it - if (!listOfAllPersonalDataRecipientsNotMentionedInInformation.contains( - nameOfPersonalDataRecipient - ) - ) - listOfAllPersonalDataRecipientsNotMentionedInInformation.add( - nameOfPersonalDataRecipient - ) - } + // if the first node is a FileWrite => A call expression that writes a literal containing information of the data recipients could be found => the code is compliant to article 19 + assertFalse(firstNode.labels().contains("FileWrite")) } - // check if the code is not compliant to article 19 - assertNotEquals(0, listOfAllPersonalDataRecipientsNotMentionedInInformation.size) } @Test @@ -344,7 +310,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"PUT\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name: \"write\"})-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = @@ -367,7 +333,7 @@ open class GDPRComplianceChecks { } } // if the code is compliant to article 20(1), the list should be empty - assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) + assertEquals(1, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) } @Test @@ -379,7 +345,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"PUT\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(m:MemberCallExpression {name: \"write\"})-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2), path4=(open:CallExpression)-[:INITIALIZER]-(:VariableDeclaration)-[:REFERS_TO]-(:DeclaredReferenceExpression)-[:BASE]-(m) WHERE open.code CONTAINS \".json\" } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = @@ -414,7 +380,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE ((d1.type=\"CREATE\") OR (d1.type=\"UPDATE\")) AND NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint)} RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: \"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint) AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability (to external // service) @@ -451,7 +417,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery) WHERE ((d1.type=\"CREATE\") OR (d1.type=\"UPDATE\")) AND NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint)} RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: \"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint) AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability (to external // service) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py index 7fc68cf..81d51b8 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/client.py @@ -21,7 +21,7 @@ def transfer_personal_data_to_another_service(personal_data): def store_personal_data_on_server(personal_data): url = 'test-online-notepad.com/store_data' - requests.put(url, json = personal_data) + requests.post(url, json = personal_data) if __name__ == '__main__': #@PseudoIdentifier diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py index 7273023..1754c26 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python/server.py @@ -43,7 +43,7 @@ def transfer_data_to_another_service(): else: return "Internal Server Error", 500 -@app.route("/store_data", methods=['PUT']) +@app.route("/store_data", methods=['POST']) def parse_data(): req = request.json data = { diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py index cdc5a79..2e12fd2 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/client.py @@ -21,7 +21,7 @@ def transfer_personal_data_to_another_service(personal_data): def store_personal_data_on_server(personal_data): url = 'test-online-notepad.com/store_data' - requests.put(url, json = personal_data) + requests.post(url, json = personal_data) if __name__ == '__main__': #@PseudoIdentifier diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py index de752ba..492688a 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation/server.py @@ -40,7 +40,7 @@ def transfer_data_to_another_service(): # VALIDATION: Personal data is not transferred to a third party return "OK", 200 -@app.route("/store_data", methods=['PUT']) +@app.route("/store_data", methods=['POST']) def parse_data(): req = request.json data = { diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py index 9480f74..8f8b829 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/client.py @@ -8,7 +8,7 @@ def delete_own_data(personal_data): def store_personal_data_on_server(personal_data): url = 'test-online-notepad.com/store_data' - requests.put(url, json = personal_data) + requests.post(url, json = personal_data) if __name__ == '__main__': #@PseudoIdentifier diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py index 2686510..de7e141 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python/server.py @@ -26,7 +26,7 @@ def parse_data(): requests.delete("ext-ad-server.com/data", json = data) return "Created", 201 -@app.route("/store_data", methods=['PUT']) +@app.route("/store_data", methods=['POST']) def parse_data(): req = request.json data = { diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py index 06cae19..8c6829a 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/client.py @@ -8,7 +8,7 @@ def delete_own_data(personal_data): def store_personal_data_on_server(personal_data): url = 'test-online-notepad.com/store_data' - requests.put(url, json = personal_data) + requests.post(url, json = personal_data) if __name__ == '__main__': diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py index 46613e4..fb7ad19 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_validation/server.py @@ -24,7 +24,7 @@ def parse_data(): # VALIDATION: no external advertising server is informed about the deletion and no deletion is performed return "OK", 200 -@app.route("/store_data", methods=['PUT']) +@app.route("/store_data", methods=['POST']) def parse_data(): req = request.json data = { diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py index e7570b7..385769b 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/client.py @@ -10,7 +10,7 @@ def rectify(personal_data): def store_personal_data_on_server(personal_data): url = 'test-online-notepad.com/store_data' - requests.put(url, json = personal_data) + requests.post(url, json = personal_data) if __name__ == '__main__': #@PseudoIdentifier diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py index cf3e771..44b7cf0 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python/server.py @@ -23,7 +23,7 @@ def parse_data(): user_db_collection.update_one({"name": data['name']}) return "Created", 201 -@app.route("/store_data", methods=['PUT']) +@app.route("/store_data", methods=['POST']) def parse_data(): req = request.json data = { diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py index ab612ee..e6a2e09 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/client.py @@ -10,7 +10,7 @@ def rectify(personal_data): def store_personal_data_on_server(personal_data): url = 'test-online-notepad.com/store_data' - requests.put(url, json = personal_data) + requests.post(url, json = personal_data) if __name__ == '__main__': #@PseudoIdentifier diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py index a78946a..8e3f7ea 100755 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_validation/server.py @@ -24,7 +24,7 @@ def parse_data(): # VALIDATION: no rectification is performed => No update call to the database return "Created", 201 -@app.route("/store_data", methods=['PUT']) +@app.route("/store_data", methods=['POST']) def parse_data(): req = request.json data = { From 77bebb2e57eebff76ce276f39d37f5c2b073876d Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 9 Apr 2023 21:40:08 +0200 Subject: [PATCH 36/39] add: missing addition to translationResult --- .../java/io/clouditor/graph/nodes/labels/PseudoIdentifier.kt | 3 ++- .../src/main/java/io/clouditor/graph/passes/FileWritePass.kt | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cloudpg/src/main/java/io/clouditor/graph/nodes/labels/PseudoIdentifier.kt b/cloudpg/src/main/java/io/clouditor/graph/nodes/labels/PseudoIdentifier.kt index 851507d..e0cde80 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/nodes/labels/PseudoIdentifier.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/nodes/labels/PseudoIdentifier.kt @@ -4,6 +4,7 @@ import de.fraunhofer.aisec.cpg.graph.Node open class PseudoIdentifier(labeledNode: Node) : DataLabel(labeledNode) { override fun areMergeable(l: Label): Boolean { - return l::class == PseudoIdentifier::class + return false + //return l::class == PseudoIdentifier::class } } diff --git a/cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt b/cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt index 4633d15..2667231 100644 --- a/cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt +++ b/cloudpg/src/main/java/io/clouditor/graph/passes/FileWritePass.kt @@ -5,6 +5,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.CallExpression import de.fraunhofer.aisec.cpg.passes.Pass import io.clouditor.graph.Application import io.clouditor.graph.FileWrite +import io.clouditor.graph.plusAssign abstract class FileWritePass: Pass() { @@ -17,6 +18,8 @@ abstract class FileWritePass: Pass() { val fileWriteNode = FileWrite(call) // Add to functionalities if necessary app?.functionalities?.plusAssign(fileWriteNode) + // Add to translation result + t += fileWriteNode return fileWriteNode } } \ No newline at end of file From f8d516d0cbc6c949ded2ed209d6d6cc0d9bf09d5 Mon Sep 17 00:00:00 2001 From: andreas Date: Mon, 10 Apr 2023 11:37:09 +0200 Subject: [PATCH 37/39] feat: created further testcases for enhancing the PPG in future work --- .../clouditor/graph/GDPRComplianceChecks.kt | 260 +++++++++++++++++- .../README.md | 4 + .../client_edit.py | 26 ++ .../client_signup.py | 16 ++ .../config.yml | 13 + .../server.py | 59 ++++ .../README.md | 4 + .../client.py | 35 +++ .../config.yml | 15 + .../server.py | 62 +++++ .../README.md | 4 + .../client_edit.py | 30 ++ .../client_signup.py | 17 ++ .../config.yml | 15 + .../server.py | 62 +++++ .../README.md | 4 + .../client_edit.py | 15 + .../client_signup.py | 16 ++ .../config.yml | 11 + .../server.py | 47 ++++ .../README.md | 4 + .../client_edit.py | 16 ++ .../client_signup.py | 16 ++ .../config.yml | 15 + .../server.py | 42 +++ 25 files changed, 803 insertions(+), 5 deletions(-) create mode 100644 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_edit.py create mode 100755 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_signup.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/server.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/client.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/server.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_edit.py create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_signup.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/server.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_edit.py create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_signup.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/server.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_edit.py create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_signup.py create mode 100644 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/config.yml create mode 100755 ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/server.py diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt index d55cf46..c49b2a8 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRComplianceChecks.kt @@ -74,6 +74,36 @@ open class GDPRComplianceChecks { assertNotEquals(0, listOfAllPseudoIdentifierWithNoUpdateByIdentity.size) } + @Test + fun checkComplianceToArticle16_same_personal_data_different_location() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location" + ), + listOf(Path(".")), + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: 'POST'})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'PUT'})-[:TO]->(he3:HttpEndpoint {method: 'PUT'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='UPDATE') AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + ) + + // create a list for all pseudoidentifiers with no update call connected to them via a data flow + val listOfAllPseudoIdentifierWithNoUpdateByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoUpdateByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoUpdateByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 16, the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoUpdateByIdentity.size) + } + @Test fun checkComplianceToArticle17_paragraph_1() { val result = @@ -132,6 +162,35 @@ open class GDPRComplianceChecks { assertNotEquals(0, listOfAllPseudoIdentifierWithNoDeleteByIdentity.size) } + @Test + fun checkComplianceToArticle17_paragraph_1_same_personal_data_different_location() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location" + ), + listOf(Path(".")), + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: 'POST'})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: 'CREATE'}) WHERE NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest {name: 'DELETE'})-[:TO]->(he3:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(d2:DatabaseQuery) WHERE (d2.type='DELETE') AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + ) + // create a list for all pseudoidentifiers with no delete call connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteByIdentity.contains(firstNode.id())) + listOfAllPseudoIdentifierWithNoDeleteByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 17(1), the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoDeleteByIdentity.size) + } + @Test fun checkComplianceToArticle17_paragraph_2() { val result = @@ -196,6 +255,38 @@ open class GDPRComplianceChecks { assertNotEquals(0, listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.size) } + @Test + fun checkComplianceToArticle17_paragraph_2_same_personal_data_different_location() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location" + ), + listOf(Path(".")), + "MATCH (hr1:HttpRequest), path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1) WHERE NOT (hr1)-[:TO]-(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr2:HttpRequest {name: 'DELETE'})-[:TO]-(he2:HttpEndpoint {method: 'DELETE'})--()-[:DFG*]->(hr3:HttpRequest) WHERE (hr3.name='DELETE') AND (hr3.url = hr1.url) AND NOT (hr3)-[:TO]-(:HttpEndpoint) } RETURN path1" + ) + // create a list for all pseudoidentifiers, which are communicated to extern with no delete call to extern connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity = mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.add(firstNode.id()) + } + } + // if the code is compliant to article 17(2), the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoDeleteToExternByIdentity.size) + } + @Test fun checkComplianceToArticle19() { val result_data_flows = @@ -301,6 +392,58 @@ open class GDPRComplianceChecks { } } + @Test + fun checkComplianceToArticle19_same_personal_data_different_location() { + val result_data_flows = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location" + ), + listOf(Path(".")), + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]->(:HttpEndpoint) AND NOT EXISTS { MATCH path2=(ps1)--()-[:DFG*]->(hr3:HttpRequest)-[:TO]->(he3:HttpEndpoint)--()-[:DFG*]->(hr4:HttpRequest) WHERE NOT (hr4)-[:TO]->(:HttpEndpoint) AND ((hr4.name='DELETE') OR (hr4.name='PUT')) AND (hr4.url = hr2.url) } RETURN path1" + ) + // create a list for all pseudoidentifiers, which are communicated to extern with no delete or update call to extern connected to them via a data flow + val listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity = mutableListOf() + // iterate over all paths and add to the list + result_data_flows.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.add( + firstNode.id() + ) + } + } + // if the code is compliant to article 19, the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoDeleteOrUpdateToExternByIdentity.size) + + val result_data_storage = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python" + ), + listOf(Path(".")), + "MATCH path1=(ps1:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest)-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]-(hr2:HttpRequest) WHERE NOT (hr2)-[:TO]-(:HttpEndpoint) WITH COLLECT(DISTINCT hr2.url) as externalDataRecipients MATCH path2=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->()<-[:DFG*]-(l2:Literal) WHERE ALL(recipient IN externalDataRecipients WHERE l2.value CONTAINS recipient) RETURN path2" + ) + // iterate over all found paths and check if the first node is a FileWrite + result_data_storage.forEach { + val path = it.get("path2") as Array<*> + // the first node is the literal, which contains the name of the personal data recipient + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + // if the first node is a FileWrite => A call expression that writes a literal containing information of the data recipients could be found => the code is compliant to article 19 + assertTrue(firstNode.labels().contains("FileWrite")) + } + } + @Test fun checkComplianceToArticle20_paragraph_1() { val result = @@ -310,7 +453,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:TO]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:TO]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = @@ -333,7 +476,7 @@ open class GDPRComplianceChecks { } } // if the code is compliant to article 20(1), the list should be empty - assertEquals(1, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) + assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) } @Test @@ -345,7 +488,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:DFG*]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:DFG*]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:TO]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:TO]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = @@ -371,6 +514,76 @@ open class GDPRComplianceChecks { assertNotEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) } + @Test + fun checkComplianceToArticle20_paragraph_1_same_personal_data_different_location() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location" + ), + listOf(Path(".")), + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:TO]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:TO]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + ) + // create a list for all pseudoidentifiers with no compliant data portability + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = + mutableListOf() + // iterate over all paths and add to the list + result.forEach { + val path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.add( + firstNode.id() + ) + } + } + // if the code is compliant to article 20(1), the list should be empty + assertEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) + } + + @Test + fun checkComplianceToArticle20_paragraph_1_no_machine_readable_format() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format" + ), + listOf(Path(".")), + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:TO]->(he1:HttpEndpoint)-[:DFG*]->(d1:DatabaseQuery {type:\"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:TO]->(he2:HttpEndpoint {method: \"GET\"})-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->({name: \"HttpStatus.OK\"}), path3=(:FileWrite)-[:CALLS]->(m:MemberCallExpression)-[:ARGUMENTS]->(:Node)<-[:DFG*]-(hr2) WHERE (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + ) + // create a list for all pseudoidentifiers with no compliant data portability + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity = + mutableListOf() + // iterate over all paths and add to the list + result.forEach { + val path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.contains( + firstNode.id() + ) + ) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.add( + firstNode.id() + ) + } + } + // if the code is compliant to article 20(1), the list should be empty => In this case we except the list is not empty, because no machine-readadble format is used + assertNotEquals(0, listOfAllPseudoIdentifierWithNoCompliantDataPortabilityByIdentity.size) + } + @Test fun checkComplianceToArticle20_paragraph_2() { val result = @@ -380,7 +593,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: \"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint) AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: \"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:TO]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint) AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability (to external // service) @@ -417,7 +630,7 @@ open class GDPRComplianceChecks { "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_validation" ), listOf(Path(".")), - "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})--()-[:DFG*]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: \"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest)--()-[:DFG*]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint) AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: \"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:TO]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint) AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" ) // create a list for all pseudoidentifiers with no compliant data portability (to external // service) @@ -444,4 +657,41 @@ open class GDPRComplianceChecks { listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size ) } + + @Test + fun checkComplianceToArticle20_paragraph_2_same_personal_data_different_location() { + val result = + executePPGAndQuery( + Path( + System.getProperty("user.dir") + + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location" + ), + listOf(Path(".")), + "MATCH path1=(psi:PseudoIdentifier)--()-[:DFG*]->(hr1:HttpRequest {name: \"POST\"})-[:TO]->(he1:HttpEndpoint)--()-[:DFG*]->(d1:DatabaseQuery {type: \"CREATE\"}) WHERE NOT EXISTS { MATCH path2=(psi)--()-[:DFG*]->(hr2:HttpRequest {name: \"GET\"})-[:TO]->(he2:HttpEndpoint)--()-[:DFG*]->(d2:DatabaseQuery {type:\"READ\"})-[:DFG*]->(hr3:HttpRequest {name: \"PUT\"}) WHERE NOT (hr3)-[:TO]-(:HttpEndpoint) AND (d1)-[:STORAGE]->(:DatabaseStorage)<-[:STORAGE]-(d2) } RETURN path1" + ) + // create a list for all pseudoidentifiers with no compliant data portability (to external + // service) + val listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity = + mutableListOf() + // iterate over all paths and add to the list + result.forEach { + var path = it.get("path1") as Array<*> + + // the first node is the pseudoidentifier because of the query + val firstNode = (path.first() as InternalPath.SelfContainedSegment).start() + if (firstNode.labels().contains("PseudoIdentifier")) { + // add the pseudoidentifier to the list if it is not already in it + if (!listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity + .contains(firstNode.id()) + ) + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity + .add(firstNode.id()) + } + } + // if the code is compliant to article 20(2), the list should be empty + assertEquals( + 0, + listOfAllPseudoIdentifierWithNoCompliantDataPortabilityToExternalServiceByIdentity.size + ) + } } diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/README.md b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/README.md new file mode 100644 index 0000000..6fcac06 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/README.md @@ -0,0 +1,4 @@ +# Test Case: Article 19 - Notification Obligation - Same Personal Data, Different Location +- Test case description: A user is registered in the "client_signup" page and his personal data is sent to a server. The server processes the data, saves it in a Mongo database and communicates parts of the personal data to a third party (external advertising server). On another page ("client_edit") the user can request deletion and rectification of his personal data, which was initially stored via signup. The server performs these requests and notifies the external advertising server about the deletion and rectification of the personal data. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 19. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_edit.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_edit.py new file mode 100755 index 0000000..0da6ebe --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_edit.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import requests + +def rectify_data(personal_data): + url = 'test-online-notepad.com/data' + personal_data["name"] = "new name" + requests.put(url, json = personal_data) + +def get_information_about_data_recipients(personal_data): + data_recipients_information = "receiver of your personal data: ext-ad-server.com/data (external advertising server)\nIt is used for the following purposes: advertising" + # create file containing the information + data_recipients_server = requests.get("test-online-notepad.com/data_recipients", params = {"auth_token": personal_data["auth_token"]}) + f = open("data_recipients_information.txt", "w") + f.write(data_recipients_server) + f.close() + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "notes": ["note1", "note2", "note3"], + "auth_token": "1234567890" + } + rectify_data(personal_data) + get_information_about_data_recipients(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_signup.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_signup.py new file mode 100755 index 0000000..98f1399 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/client_signup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import requests + +def send_data_to_server(personal_data): + url = 'test-online-notepad.com/data' + requests.post(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "notes": ["note1", "note2", "note3"], + "auth_token": "1234567890" + } + send_data_to_server(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/config.yml b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/config.yml new file mode 100644 index 0000000..7180571 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/config.yml @@ -0,0 +1,13 @@ +services: + - type: server + name: server + host: test-online-notepad.com + - type: client + name: client + - type: external-advertising-server + name: external-advertising-server + host: ext-ad-server.com + - type: db + name: mongo + storages: + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/server.py b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/server.py new file mode 100755 index 0000000..5ab712a --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/NotificationObligation/Python_same_personal_data_different_location/server.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +mongo_host = "mongo" +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +data_recipients_information = "receiver of your personal data: ext-ad-server.com/data (external advertising server)\nIt is used for the following purposes: advertising" + +@app.route("/data", methods=['DELETE']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + + user_db_collection.delete_one({"username": data['username']}) + # inform external advertising server about the deletion + requests.delete("ext1-ad-server.com/data", json = data) + return "OK", 200 + +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + url = "ext-ad-server.com/data" + user_db_collection.update_one({"username": data['username']}, {"$set": {"notes": data['notes']}}) + # inform external advertising server about the rectification + requests.put(url, json = data) + return "OK", 200 + +@app.route("/data", methods=['POST']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + url = "ext-ad-server.com/data" + # send notes to external advertising server + requests.post("ext-ad-server.com/data", json = data['notes']) + return "OK", 200 + +@app.route("/data_recipients", methods=['GET']) +def parse_data(): + return data_recipients_information, 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/README.md new file mode 100644 index 0000000..ab5e7ed --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/README.md @@ -0,0 +1,4 @@ +# Test Case: Article 20 - Right to Data Portability - No Machine Readable Format +- Test case description: The client sends personal data to the server. The server processes the data, saves it in a Mongo database. The client offers a function for the retrieval of his personal data. A file is created containing in the personal data, but the created file is not in a machine-readable format. +- Expected outcome: + - A data flow is detected which does not fulfill the code properties of GDPR article 20. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/client.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/client.py new file mode 100755 index 0000000..cc3f7aa --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/client.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 + +import requests +import os + +def get_personal_data_in_machine_readable_format(personal_data): + url = 'test-online-notepad.com/data' + # get the data from the server + personal_data_received = requests.get(url, json = personal_data) + f = open("personal_data.txt", "w") + f.write(personal_data_received) + f.close() + +def transfer_personal_data_to_another_service(personal_data): + url = 'test-online-notepad.com/transfer' + data = { + "receiver_url": "other-test-online-notepad.com/data", + "personal_data": personal_data + } + requests.get(url, json = data) + +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.post(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data_of_client = { + "username": "testuser", + "name": "", + "notes": "" + } + store_personal_data_on_server(personal_data_of_client) + get_personal_data_in_machine_readable_format(personal_data_of_client) + transfer_personal_data_to_another_service(personal_data_of_client) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/config.yml new file mode 100644 index 0000000..bdc9375 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/config.yml @@ -0,0 +1,15 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + name: mongo + storages: + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/server.py new file mode 100755 index 0000000..1754c26 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_no_machine_readable_format/server.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +mongo_host = "mongo" +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['GET']) +def get_data_in_csv_format(): + req = request.json + data = { + "username": req['username'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['username']}) + # send the data to the client + return user_data, 200 + +@app.route("/transfer", methods=['GET']) +def transfer_data_to_another_service(): + req = request.json + data = { + "receiver_url": req['receiver_url'], + "personal_data": req['personal_data'] + } + if user_db_collection.find( { "username": data['personal_data']['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['personal_data']['username']}) + response = requests.put(data['receiver_url'], json = user_data) + if response.status_code == 201: + return "OK", 200 + else: + return "Internal Server Error", 500 + +@app.route("/store_data", methods=['POST']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + return "OK", 200 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/README.md new file mode 100644 index 0000000..6b7d235 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/README.md @@ -0,0 +1,4 @@ +# Test Case: Article 20 - Right to Data Portability - Same Personal Data, Different Location +- Test case description: A user is registered in the "client_signup" page and his personal data is sent to a server. The server processes the data, saves it in a Mongo database. On another page ("client_edit") the user can request retrieval of his stored personal data (via signup) in a machine-readable format, as well as the transfer of his personal data to another data controller. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 20. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_edit.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_edit.py new file mode 100755 index 0000000..86ac90f --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_edit.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import requests +import os + +def get_personal_data_in_machine_readable_format(personal_data): + url = 'test-online-notepad.com/data' + # get the data from the server + personal_data_received = requests.get(url, json = personal_data) + f = open("personal_data.json", "w") + f.write(personal_data_received) + f.close() + +def transfer_personal_data_to_another_service(personal_data): + url = 'test-online-notepad.com/transfer' + data = { + "receiver_url": "other-test-online-notepad.com/data", + "personal_data": personal_data + } + requests.get(url, json = data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data_of_client = { + "username": "testuser", + "name": "", + "notes": "" + } + get_personal_data_in_machine_readable_format(personal_data_of_client) + transfer_personal_data_to_another_service(personal_data_of_client) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_signup.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_signup.py new file mode 100755 index 0000000..e89e2bc --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/client_signup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import requests +import os + +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.post(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data_of_client = { + "username": "testuser", + "name": "", + "notes": "" + } + store_personal_data_on_server(personal_data_of_client) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/config.yml new file mode 100644 index 0000000..bdc9375 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/config.yml @@ -0,0 +1,15 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + name: mongo + storages: + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/server.py new file mode 100755 index 0000000..1754c26 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python_same_personal_data_different_location/server.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +mongo_host = "mongo" +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['GET']) +def get_data_in_csv_format(): + req = request.json + data = { + "username": req['username'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['username']}) + # send the data to the client + return user_data, 200 + +@app.route("/transfer", methods=['GET']) +def transfer_data_to_another_service(): + req = request.json + data = { + "receiver_url": req['receiver_url'], + "personal_data": req['personal_data'] + } + if user_db_collection.find( { "username": data['personal_data']['username'] } ).count() > 0: + return "Conflict", 409 + else: + # get the data from the database (mongodb) + user_data = user_db_collection.find_one({"username": data['personal_data']['username']}) + response = requests.put(data['receiver_url'], json = user_data) + if response.status_code == 201: + return "OK", 200 + else: + return "Internal Server Error", 500 + +@app.route("/store_data", methods=['POST']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + return "OK", 200 + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/README.md new file mode 100644 index 0000000..ef570d3 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/README.md @@ -0,0 +1,4 @@ +# Test Case: Article 17 - Right to Erasure - Same Personal Data, Different Location +- Test case description: A user is registered in the "client_signup" page and his personal data is sent to a server. The server processes the data, saves it in a Mongo database and sends parts of it to third parties. On another page ("client_edit") the user can request deletion of his personal data, which was initially stored via signup. The server performs this request, deletes the personal data and informs other data recipients about the deletion request. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 17. \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_edit.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_edit.py new file mode 100755 index 0000000..355d97e --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_edit.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +import requests + +def delete_own_data(personal_data): + url = 'test-online-notepad.com/data' + requests.delete(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "notes": ["note1", "note2", "note3"] + } + delete_own_data(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_signup.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_signup.py new file mode 100755 index 0000000..d5e3bc1 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/client_signup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import requests + +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.post(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "firstname lastname", + "notes": ["note1", "note2", "note3"] + } + store_personal_data_on_server(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/config.yml new file mode 100644 index 0000000..8ddc973 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/config.yml @@ -0,0 +1,11 @@ +services: + - type: server + name: server + host: test-online-notepad.com + - type: client + name: client + - type: db + name: mongo + storages: + - userdata + - otherdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/server.py new file mode 100755 index 0000000..de7e141 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToErasure/Python_same_personal_data_different_location/server.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +import requests + +mongo_host = "mongo" +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['DELETE']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + user_db_collection.delete_one({"username": data['username']}) + # inform external advertising server about the deletion + requests.delete("ext-ad-server.com/data", json = data) + return "Created", 201 + +@app.route("/store_data", methods=['POST']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "notes": req['notes'] + } + if user_db_collection.find( { "username": data['username'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + # send data to external advertising server + url = 'ext-ad-server.com/data' + requests.put(url, json = data) + return "OK", 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md new file mode 100644 index 0000000..7b82364 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md @@ -0,0 +1,4 @@ +# Test Case: Article 16 - Right to Rectification - Same Personal Data, Different Location +- Test case description: TA user is registered in the "client_signup" page and his personal data is sent to a server. The server processes the data, saves it in a Mongo database. On another page ("client_edit") the user can request rectification of his personal data, which was initially stored via signup. The server performs this request and rectifies the personal data and stores the updated data in the database. +- Expected outcome: + - No data flow is detected which does not fulfill the code properties of GDPR article 16. diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_edit.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_edit.py new file mode 100755 index 0000000..9e451e5 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_edit.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import requests + +def rectify(personal_data): + url = 'test-online-notepad.com/data' + personal_data["name"] = "new name" + requests.put(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data_1 = { + "username": "testuser", + "name": "firstname lastname" + } + rectify(personal_data_1) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_signup.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_signup.py new file mode 100755 index 0000000..d5e3bc1 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/client_signup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import requests + +def store_personal_data_on_server(personal_data): + url = 'test-online-notepad.com/store_data' + requests.post(url, json = personal_data) + +if __name__ == '__main__': + #@PseudoIdentifier + personal_data = { + "username": "testuser", + "name": "firstname lastname", + "notes": ["note1", "note2", "note3"] + } + store_personal_data_on_server(personal_data) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/config.yml b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/config.yml new file mode 100644 index 0000000..bdc9375 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/config.yml @@ -0,0 +1,15 @@ +services: + - type: server + directory: server + name: server + host: test-online-notepad.com + - type: db + directory: server + name: postgres + storages: + - userdata + - otherdata + - type: db + name: mongo + storages: + - userdata \ No newline at end of file diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/server.py b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/server.py new file mode 100755 index 0000000..44b7cf0 --- /dev/null +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/server.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +from flask import Flask, request +from pymongo import MongoClient, database +mongo_host = "mongo" +user_db_client = MongoClient("mongodb://mongo:27017/") +user_db = user_db_client.userdata +user_db_collection = user_db.records + +app = Flask(__name__) + +@app.route("/data", methods=['PUT']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "name": req['name'], + "notes": req['notes'] + } + if user_db_collection.find( { "name": data['name'] } ).count() > 0: + return "Conflict", 409 + else: + user_db_collection.update_one({"name": data['name']}) + return "Created", 201 + +@app.route("/store_data", methods=['POST']) +def parse_data(): + req = request.json + data = { + "username": req['username'], + "name": req['name'], + "notes": req['notes'] + } + if user_db_collection.find( { "name": data['name'] } ).count() > 0: + return "Conflict", 409 + else: + # save data to database + user_db_collection.insert_one(data) + return "OK", 200 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8080, debug=True, threaded=True) \ No newline at end of file From e537e744b888ce6b2955b7196e8c2d958b5cf572 Mon Sep 17 00:00:00 2001 From: andreas Date: Mon, 10 Apr 2023 12:59:30 +0200 Subject: [PATCH 38/39] fix typo --- .../Python_same_personal_data_different_location/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md index 7b82364..69fcbf2 100644 --- a/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md +++ b/ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python_same_personal_data_different_location/README.md @@ -1,4 +1,4 @@ # Test Case: Article 16 - Right to Rectification - Same Personal Data, Different Location -- Test case description: TA user is registered in the "client_signup" page and his personal data is sent to a server. The server processes the data, saves it in a Mongo database. On another page ("client_edit") the user can request rectification of his personal data, which was initially stored via signup. The server performs this request and rectifies the personal data and stores the updated data in the database. +- Test case description: A user is registered in the "client_signup" page and his personal data is sent to a server. The server processes the data, saves it in a Mongo database. On another page ("client_edit") the user can request rectification of his personal data, which was initially stored via signup. The server performs this request and rectifies the personal data and stores the updated data in the database. - Expected outcome: - No data flow is detected which does not fulfill the code properties of GDPR article 16. From 969dbcab0eca086c3ece25a2f6309c9f73d83dc4 Mon Sep 17 00:00:00 2001 From: andreas Date: Sat, 15 Apr 2023 11:27:31 +0200 Subject: [PATCH 39/39] refactor: changed code to be tested to Article 20 testcase code --- .../java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt b/cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt index 732ce0f..3f94c63 100644 --- a/cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt +++ b/cloudpg/src/test/java/io/clouditor/graph/GDPRExtensionPerformanceTest.kt @@ -31,7 +31,7 @@ open class GDPRExtensionPerformanceTest { executePPG( Path( System.getProperty("user.dir") + - "/../ppg-testing-library/GDPRComplianceChecks/RightToRectification/Python" + "/../ppg-testing-library/GDPRComplianceChecks/RightToDataPortability/Python" ), listOf(Path(".")) )