summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-02 14:26:23 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-02 18:07:42 +0200
commit45b73e4683cce35de79117c5b4a6919556d9644f (patch)
treefdbb282b639d03e0cc940c8587d5fe90c2283aa5
parente2ec16a1a40f3ffc437378b4e22fda64f86fe284 (diff)
api: Add stricter validation of input/output data
This change adds stricter validation of data that enters and leaves the database. As a result, we clearly separate the database model from the data model that the REST API exports.
-rw-r--r--opendc-web/opendc-web-api/opendc/api/portfolios.py9
-rw-r--r--opendc-web/opendc-web-api/opendc/api/prefabs.py19
-rw-r--r--opendc-web/opendc-web-api/opendc/api/projects.py28
-rw-r--r--opendc-web/opendc-web-api/opendc/api/scenarios.py7
-rw-r--r--opendc-web/opendc-web-api/opendc/api/topologies.py10
-rw-r--r--opendc-web/opendc-web-api/opendc/api/traces.py6
-rw-r--r--opendc-web/opendc-web-api/opendc/auth.py3
-rw-r--r--opendc-web/opendc-web-api/opendc/database.py11
-rw-r--r--opendc-web/opendc-web-api/opendc/exts.py4
-rw-r--r--opendc-web/opendc-web-api/opendc/models/portfolio.py2
-rw-r--r--opendc-web/opendc-web-api/opendc/models/prefab.py3
-rw-r--r--opendc-web/opendc-web-api/opendc/models/project.py13
-rw-r--r--opendc-web/opendc-web-api/opendc/models/scenario.py24
-rw-r--r--opendc-web/opendc-web-api/opendc/models/topology.py4
-rw-r--r--opendc-web/opendc-web-api/opendc/models/trace.py9
-rw-r--r--opendc-web/opendc-web-ui/src/api/topologies.js3
16 files changed, 96 insertions, 59 deletions
diff --git a/opendc-web/opendc-web-api/opendc/api/portfolios.py b/opendc-web/opendc-web-api/opendc/api/portfolios.py
index b07e9da5..84ec466c 100644
--- a/opendc-web/opendc-web-api/opendc/api/portfolios.py
+++ b/opendc-web/opendc-web-api/opendc/api/portfolios.py
@@ -44,7 +44,7 @@ class Portfolio(Resource):
portfolio.check_exists()
portfolio.check_user_access(current_user['sub'], False)
- data = portfolio.obj
+ data = PortfolioSchema().dump(portfolio.obj)
return {'data': data}
def put(self, portfolio_id):
@@ -63,7 +63,7 @@ class Portfolio(Resource):
portfolio.set_property('targets.repeatsPerScenario', result['portfolio']['targets']['repeatsPerScenario'])
portfolio.update()
- data = portfolio.obj
+ data = PortfolioSchema().dump(portfolio.obj)
return {'data': data}
def delete(self, portfolio_id):
@@ -84,7 +84,8 @@ class Portfolio(Resource):
project.update()
old_object = portfolio.delete()
- return {'data': old_object}
+ data = PortfolioSchema().dump(old_object)
+ return {'data': data}
class PutSchema(Schema):
"""
@@ -125,7 +126,7 @@ class PortfolioScenarios(Resource):
portfolio.obj['scenarioIds'].append(scenario.get_id())
portfolio.update()
- data = scenario.obj
+ data = ScenarioSchema().dump(scenario.obj)
return {'data': data}
class PostSchema(Schema):
diff --git a/opendc-web/opendc-web-api/opendc/api/prefabs.py b/opendc-web/opendc-web-api/opendc/api/prefabs.py
index 7bb17e7d..730546ba 100644
--- a/opendc-web/opendc-web-api/opendc/api/prefabs.py
+++ b/opendc-web/opendc-web-api/opendc/api/prefabs.py
@@ -24,7 +24,6 @@ from flask_restful import Resource
from marshmallow import Schema, fields
from opendc.models.prefab import Prefab as PrefabModel, PrefabSchema
-from opendc.database import Database
from opendc.exts import current_user, requires_auth, db
@@ -56,14 +55,15 @@ class PrefabList(Resource):
result = schema.load(request.json)
prefab = PrefabModel(result['prefab'])
- prefab.set_property('datetimeCreated', Database.datetime_to_string(datetime.now()))
- prefab.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now()))
+ prefab.set_property('datetimeCreated', datetime.now())
+ prefab.set_property('datetimeLastEdited', datetime.now())
user_id = current_user['sub']
prefab.set_property('authorId', user_id)
prefab.insert()
- return {'data': prefab.obj}
+ data = PrefabSchema().dump(prefab.obj)
+ return {'data': data}
class PostSchema(Schema):
"""
@@ -83,7 +83,8 @@ class Prefab(Resource):
prefab = PrefabModel.from_id(prefab_id)
prefab.check_exists()
prefab.check_user_access(current_user['sub'])
- return {'data': prefab.obj}
+ data = PrefabSchema().dump(prefab.obj)
+ return {'data': data}
def put(self, prefab_id):
"""Update a prefab's name and/or contents."""
@@ -97,10 +98,11 @@ class Prefab(Resource):
prefab.set_property('name', result['prefab']['name'])
prefab.set_property('rack', result['prefab']['rack'])
- prefab.set_property('datetime_last_edited', Database.datetime_to_string(datetime.now()))
+ prefab.set_property('datetimeLastEdited', datetime.now())
prefab.update()
- return {'data': prefab.obj}
+ data = PrefabSchema().dump(prefab.obj)
+ return {'data': data}
def delete(self, prefab_id):
"""Delete this Prefab."""
@@ -111,7 +113,8 @@ class Prefab(Resource):
old_object = prefab.delete()
- return {'data': old_object}
+ data = PrefabSchema().dump(old_object)
+ return {'data': data}
class PutSchema(Schema):
"""
diff --git a/opendc-web/opendc-web-api/opendc/api/projects.py b/opendc-web/opendc-web-api/opendc/api/projects.py
index 8c44b680..05f02a84 100644
--- a/opendc-web/opendc-web-api/opendc/api/projects.py
+++ b/opendc-web/opendc-web-api/opendc/api/projects.py
@@ -27,7 +27,6 @@ from opendc.models.portfolio import Portfolio, PortfolioSchema
from opendc.models.topology import Topology, TopologySchema
from opendc.models.project import Project as ProjectModel, ProjectSchema
from opendc.exts import current_user, requires_auth
-from opendc.database import Database
class ProjectList(Resource):
@@ -40,7 +39,8 @@ class ProjectList(Resource):
"""Get the authorized projects of the user"""
user_id = current_user['sub']
projects = ProjectModel.get_for_user(user_id)
- return {'data': projects}
+ data = ProjectSchema().dump(projects, many=True)
+ return {'data': data}
def post(self):
"""Create a new project, and return that new project."""
@@ -53,8 +53,8 @@ class ProjectList(Resource):
topology.insert()
project = ProjectModel(result['project'])
- project.set_property('datetimeCreated', Database.datetime_to_string(datetime.now()))
- project.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now()))
+ project.set_property('datetimeCreated', datetime.now())
+ project.set_property('datetimeLastEdited', datetime.now())
project.set_property('topologyIds', [topology.get_id()])
project.set_property('portfolioIds', [])
project.set_property('authorizations', [{'userId': user_id, 'level': 'OWN'}])
@@ -63,7 +63,8 @@ class ProjectList(Resource):
topology.set_property('projectId', project.get_id())
topology.update()
- return {'data': project.obj}
+ data = ProjectSchema().dump(project.obj)
+ return {'data': data}
class Project(Resource):
@@ -79,7 +80,8 @@ class Project(Resource):
project.check_exists()
project.check_user_access(current_user['sub'], False)
- return {'data': project.obj}
+ data = ProjectSchema().dump(project.obj)
+ return {'data': data}
def put(self, project_id):
"""Update a project's name."""
@@ -92,10 +94,11 @@ class Project(Resource):
project.check_user_access(current_user['sub'], True)
project.set_property('name', result['project']['name'])
- project.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now()))
+ project.set_property('datetimeLastEdited', datetime.now())
project.update()
- return {'data': project.obj}
+ data = ProjectSchema().dump(project.obj)
+ return {'data': data}
def delete(self, project_id):
"""Delete this Project."""
@@ -113,8 +116,8 @@ class Project(Resource):
portfolio.delete()
old_object = project.delete()
-
- return {'data': old_object}
+ data = ProjectSchema().dump(old_object)
+ return {'data': data}
class PutSchema(Schema):
"""
@@ -148,10 +151,11 @@ class ProjectTopologies(Resource):
topology.insert()
project.obj['topologyIds'].append(topology.get_id())
- project.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now()))
+ project.set_property('datetimeLastEdited', datetime.now())
project.update()
- return {'data': topology.obj}
+ data = TopologySchema().dump(topology.obj)
+ return {'data': data}
class PutSchema(Schema):
"""
diff --git a/opendc-web/opendc-web-api/opendc/api/scenarios.py b/opendc-web/opendc-web-api/opendc/api/scenarios.py
index b566950a..234bdec1 100644
--- a/opendc-web/opendc-web-api/opendc/api/scenarios.py
+++ b/opendc-web/opendc-web-api/opendc/api/scenarios.py
@@ -38,7 +38,7 @@ class Scenario(Resource):
scenario = ScenarioModel.from_id(scenario_id)
scenario.check_exists()
scenario.check_user_access(current_user['sub'], False)
- data = scenario.obj
+ data = ScenarioSchema().dump(scenario.obj)
return {'data': data}
def put(self, scenario_id):
@@ -54,7 +54,7 @@ class Scenario(Resource):
scenario.set_property('name', result['scenario']['name'])
scenario.update()
- data = scenario.obj
+ data = ScenarioSchema().dump(scenario.obj)
return {'data': data}
def delete(self, scenario_id):
@@ -72,7 +72,8 @@ class Scenario(Resource):
portfolio.update()
old_object = scenario.delete()
- return {'data': old_object}
+ data = ScenarioSchema().dump(old_object)
+ return {'data': data}
class PutSchema(Schema):
"""
diff --git a/opendc-web/opendc-web-api/opendc/api/topologies.py b/opendc-web/opendc-web-api/opendc/api/topologies.py
index eedf049d..a2d3f41a 100644
--- a/opendc-web/opendc-web-api/opendc/api/topologies.py
+++ b/opendc-web/opendc-web-api/opendc/api/topologies.py
@@ -24,7 +24,6 @@ from flask import request
from flask_restful import Resource
from marshmallow import Schema, fields
-from opendc.database import Database
from opendc.models.project import Project
from opendc.models.topology import Topology as TopologyModel, TopologySchema
from opendc.exts import current_user, requires_auth
@@ -43,7 +42,7 @@ class Topology(Resource):
topology = TopologyModel.from_id(topology_id)
topology.check_exists()
topology.check_user_access(current_user['sub'], False)
- data = topology.obj
+ data = TopologySchema().dump(topology.obj)
return {'data': data}
def put(self, topology_id):
@@ -60,10 +59,10 @@ class Topology(Resource):
topology.set_property('name', result['topology']['name'])
topology.set_property('rooms', result['topology']['rooms'])
- topology.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now()))
+ topology.set_property('datetimeLastEdited', datetime.now())
topology.update()
- data = topology.obj
+ data = TopologySchema().dump(topology.obj)
return {'data': data}
def delete(self, topology_id):
@@ -84,7 +83,8 @@ class Topology(Resource):
project.update()
old_object = topology.delete()
- return {'data': old_object}
+ data = TopologySchema().dump(old_object)
+ return {'data': data}
class PutSchema(Schema):
"""
diff --git a/opendc-web/opendc-web-api/opendc/api/traces.py b/opendc-web/opendc-web-api/opendc/api/traces.py
index f685f00c..6be8c5e5 100644
--- a/opendc-web/opendc-web-api/opendc/api/traces.py
+++ b/opendc-web/opendc-web-api/opendc/api/traces.py
@@ -21,7 +21,7 @@
from flask_restful import Resource
from opendc.exts import requires_auth
-from opendc.models.trace import Trace as TraceModel
+from opendc.models.trace import Trace as TraceModel, TraceSchema
class TraceList(Resource):
@@ -33,7 +33,7 @@ class TraceList(Resource):
def get(self):
"""Get all available Traces."""
traces = TraceModel.get_all()
- data = traces.obj
+ data = TraceSchema().dump(traces.obj, many=True)
return {'data': data}
@@ -47,5 +47,5 @@ class Trace(Resource):
"""Get trace information by identifier."""
trace = TraceModel.from_id(trace_id)
trace.check_exists()
- data = trace.obj
+ data = TraceSchema().dump(trace.obj)
return {'data': data}
diff --git a/opendc-web/opendc-web-api/opendc/auth.py b/opendc-web/opendc-web-api/opendc/auth.py
index 1870f01c..6db06fb1 100644
--- a/opendc-web/opendc-web-api/opendc/auth.py
+++ b/opendc-web/opendc-web-api/opendc/auth.py
@@ -42,8 +42,7 @@ def get_token():
if parts[0].lower() != "bearer":
raise AuthError({
"code": "invalid_header",
- "description": "Authorization header must start with"
- " Bearer"
+ "description": "Authorization header must start with Bearer"
}, 401)
if len(parts) == 1:
raise AuthError({"code": "invalid_header", "description": "Token not found"}, 401)
diff --git a/opendc-web/opendc-web-api/opendc/database.py b/opendc-web/opendc-web-api/opendc/database.py
index f9a33b66..37fd1a4d 100644
--- a/opendc-web/opendc-web-api/opendc/database.py
+++ b/opendc-web/opendc-web-api/opendc/database.py
@@ -19,7 +19,6 @@
# SOFTWARE.
import urllib.parse
-from datetime import datetime
from pymongo import MongoClient
@@ -90,13 +89,3 @@ class Database:
The query needs to be in json format, i.e.: `{'name': prefab_name}`.
"""
getattr(self.opendc_db, collection).delete_many(query)
-
- @staticmethod
- def datetime_to_string(datetime_to_convert):
- """Return a database-compatible string representation of the given datetime object."""
- return datetime_to_convert.strftime(DATETIME_STRING_FORMAT)
-
- @staticmethod
- def string_to_datetime(string_to_convert):
- """Return a datetime corresponding to the given string representation."""
- return datetime.strptime(string_to_convert, DATETIME_STRING_FORMAT)
diff --git a/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py
index f088a29c..d24f7197 100644
--- a/opendc-web/opendc-web-api/opendc/exts.py
+++ b/opendc-web/opendc-web-api/opendc/exts.py
@@ -34,8 +34,7 @@ def get_auth_context():
_auth_context = AuthContext(
alg=AsymmetricJwtAlgorithm(jwks_url=f"https://{os.environ['AUTH0_DOMAIN']}/.well-known/jwks.json"),
issuer=f"https://{os.environ['AUTH0_DOMAIN']}/",
- audience=os.environ['AUTH0_AUDIENCE']
- )
+ audience=os.environ['AUTH0_AUDIENCE'])
g.auth_context = _auth_context
return _auth_context
@@ -46,7 +45,6 @@ auth_context = LocalProxy(get_auth_context)
def requires_auth(f):
"""Decorator to determine if the Access Token is valid.
"""
-
@wraps(f)
def decorated(*args, **kwargs):
token = get_token()
diff --git a/opendc-web/opendc-web-api/opendc/models/portfolio.py b/opendc-web/opendc-web-api/opendc/models/portfolio.py
index aff1d3f0..1643e23e 100644
--- a/opendc-web/opendc-web-api/opendc/models/portfolio.py
+++ b/opendc-web/opendc-web-api/opendc/models/portfolio.py
@@ -16,7 +16,7 @@ class PortfolioSchema(Schema):
"""
Schema representing a portfolio.
"""
- _id = fields.String()
+ _id = fields.String(dump_only=True)
projectId = fields.String()
name = fields.String(required=True)
scenarioIds = fields.List(fields.String())
diff --git a/opendc-web/opendc-web-api/opendc/models/prefab.py b/opendc-web/opendc-web-api/opendc/models/prefab.py
index d83ef4cb..5e4b81dc 100644
--- a/opendc-web/opendc-web-api/opendc/models/prefab.py
+++ b/opendc-web/opendc-web-api/opendc/models/prefab.py
@@ -9,7 +9,8 @@ class PrefabSchema(Schema):
"""
Schema for a Prefab.
"""
- _id = fields.String()
+ _id = fields.String(dump_only=True)
+ authorId = fields.String(dump_only=True)
name = fields.String(required=True)
datetimeCreated = fields.DateTime()
datetimeLastEdited = fields.DateTime()
diff --git a/opendc-web/opendc-web-api/opendc/models/project.py b/opendc-web/opendc-web-api/opendc/models/project.py
index ee84c73e..f2b3b564 100644
--- a/opendc-web/opendc-web-api/opendc/models/project.py
+++ b/opendc-web/opendc-web-api/opendc/models/project.py
@@ -1,20 +1,29 @@
-from marshmallow import Schema, fields
+from marshmallow import Schema, fields, validate
from werkzeug.exceptions import Forbidden
from opendc.models.model import Model
from opendc.exts import db
+class ProjectAuthorizations(Schema):
+ """
+ Schema representing a project authorization.
+ """
+ userId = fields.String(required=True)
+ level = fields.String(required=True, validate=validate.OneOf(["VIEW", "EDIT", "OWN"]))
+
+
class ProjectSchema(Schema):
"""
Schema representing a Project.
"""
- _id = fields.String()
+ _id = fields.String(dump_only=True)
name = fields.String(required=True)
datetimeCreated = fields.DateTime()
datetimeLastEdited = fields.DateTime()
topologyIds = fields.List(fields.String())
portfolioIds = fields.List(fields.String())
+ authorizations = fields.List(fields.Nested(ProjectAuthorizations))
class Project(Model):
diff --git a/opendc-web/opendc-web-api/opendc/models/scenario.py b/opendc-web/opendc-web-api/opendc/models/scenario.py
index 2911b1ae..658d790e 100644
--- a/opendc-web/opendc-web-api/opendc/models/scenario.py
+++ b/opendc-web/opendc-web-api/opendc/models/scenario.py
@@ -34,17 +34,39 @@ class OperationalSchema(Schema):
schedulerName = fields.String()
+class ResultSchema(Schema):
+ """
+ Schema representing the simulation results.
+ """
+ max_num_deployed_images = fields.List(fields.Number())
+ max_cpu_demand = fields.List(fields.Number())
+ max_cpu_usage = fields.List(fields.Number())
+ mean_num_deployed_images = fields.List(fields.Number())
+ total_failure_slices = fields.List(fields.Number())
+ total_failure_vm_slices = fields.List(fields.Number())
+ total_granted_burst = fields.List(fields.Number())
+ total_interfered_burst = fields.List(fields.Number())
+ total_overcommitted_burst = fields.List(fields.Number())
+ total_power_draw = fields.List(fields.Number())
+ total_requested_burst = fields.List(fields.Number())
+ total_vms_failed = fields.List(fields.Number())
+ total_vms_finished = fields.List(fields.Number())
+ total_vms_queued = fields.List(fields.Number())
+ total_vms_submitted = fields.List(fields.Number())
+
+
class ScenarioSchema(Schema):
"""
Schema representing a scenario.
"""
- _id = fields.String()
+ _id = fields.String(dump_only=True)
portfolioId = fields.String()
name = fields.String(required=True)
simulation = fields.Nested(SimulationSchema)
trace = fields.Nested(TraceSchema)
topology = fields.Nested(TopologySchema)
operational = fields.Nested(OperationalSchema)
+ results = fields.Nested(ResultSchema, dump_only=True)
class Scenario(Model):
diff --git a/opendc-web/opendc-web-api/opendc/models/topology.py b/opendc-web/opendc-web-api/opendc/models/topology.py
index c6354ae6..71d2cade 100644
--- a/opendc-web/opendc-web-api/opendc/models/topology.py
+++ b/opendc-web/opendc-web-api/opendc/models/topology.py
@@ -72,8 +72,8 @@ class TopologySchema(Schema):
"""
Schema representing a datacenter topology.
"""
- _id = fields.String()
- projectId = fields.String()
+ _id = fields.String(dump_only=True)
+ projectId = fields.String(dump_only=True)
name = fields.String(required=True)
rooms = fields.List(fields.Nested(RoomSchema), required=True)
diff --git a/opendc-web/opendc-web-api/opendc/models/trace.py b/opendc-web/opendc-web-api/opendc/models/trace.py
index 2f6e4926..69287f29 100644
--- a/opendc-web/opendc-web-api/opendc/models/trace.py
+++ b/opendc-web/opendc-web-api/opendc/models/trace.py
@@ -1,6 +1,15 @@
+from marshmallow import Schema, fields
+
from opendc.models.model import Model
+class TraceSchema(Schema):
+ """Schema for a Trace."""
+ _id = fields.String(dump_only=True)
+ name = fields.String()
+ type = fields.String()
+
+
class Trace(Model):
"""Model representing a Trace."""
diff --git a/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js
index c8744e6c..802be4bb 100644
--- a/opendc-web/opendc-web-ui/src/api/topologies.js
+++ b/opendc-web/opendc-web-ui/src/api/topologies.js
@@ -31,7 +31,8 @@ export function getTopology(auth, topologyId) {
}
export function updateTopology(auth, topology) {
- return request(auth, `topologies/${topology._id}`, 'PUT', { topology })
+ const { _id, ...data } = topology;
+ return request(auth, `topologies/${topology._id}`, 'PUT', { topology: data })
}
export function deleteTopology(auth, topologyId) {