Security by design: client API
In een vorig blog Security by design: architectuur Mendix hebben wij de architectuur van een Mendix app beschreven. Daar werd melding gemaakt van de client API, een interface voor widgets en custom Javascript code naar de runtime server. We zullen in deze blog dieper ingaan op de mogelijkheden van de client API. Het is namelijk goed om bij het ontwerp van een veilige Mendix app zich te realiseren dat deze API door kwaadwillende gebruikers gebruikt kan worden. In volgende blogs zullen we maatregelen hiertegen bespreken.
Sniffen van internetverkeer
Het is mogelijk de communicatie tussen de Mendix client en runtime server zichtbaar te maken (sniffen). Dat kan met een separate debugging proxy (Fiddler, BURP Suite) of via de Developer Tools in de browser. Wij maken bij voorkeur gebruik van Firefox. Alle http requests en responses van en naar de browser worden bewaard en inzichtelijk gemaakt. Requests kunnen opnieuw worden uitgevoerd, eventueel na aanpassing van de inhoud.
Het bekijken van het internetverkeer tijdens het gebruik van de Mendix app geeft een goed beeld van de communicatiepatronen tussen de Mendix client en server.
Mendix client API
De Mendix client API is bedoeld om het communiceren met de runtime server voor widgets en custom Javascript te vergemakkelijken. Grofweg is de volgende functionaliteit beschikbaar:
- Ophalen van de metadata van alle domain models van de app;
- informatie over de user en sessie;
- CRUD-operaties op alle entiteiten, waarbij XPath, limit en offset kan worden gebruikt voor het selecteren van lijsten objecten;
- aanroepen van microflows met parameters;
- ophalen van resources (images, files);
- hulpmiddelen voor widgets, zoals DOM-manipulatie, validaties, formatters en listeners van mutaties op objecten.
De details van de API zijn beschreven op de website van Mendix Docs.
De client API is beschikbaar als Javascript library (mxui.js) als de Mendix client in de browser geladen is na het inloggen van de gebruiker, als er een Mendix page zichtbaar is (de DOM geladen is). Firefox (en ook Chrome) heeft de mogelijkheid om in het Console van de Web Developers tools Javascript code uit te voeren (injecteren). Op deze wijze kan de client API eenvoudig aangeroepen worden.
Client API in actie
De belangrijkste functionaliteit van de client API is selectie en mutatie van objecten en het aanroepen van microflows. We zullen hier in een aantal voorbeelden laten zien hoe dat werkt en welk internetverkeer dit teweegbrengt. Omdat elke app andere entiteiten en microflows heeft, zijn hieronder voorbeelden gegeven op basis van standaard Mendix modules (User en Administration).
Retrieve UserRoles
Commando voor het ophalen van alle gebruikersrollen:
mx.data.get({xpath: "//System.UserRole", callback: function(objs){}});
Request body:
{"action":"retrieve_by_xpath", "params":{"xpath":"//System.UserRole",
"schema":{},"count":false},"profiledata":{"1652996814399-14":170}}
Response body:
{"mxobjects":[{"attributes":{"Description":{"value":"","readonly":true},
"Name":{"value":"Medewerker","readonly":true}},"guid":"2533274790396108",
"hash":"WUdi6SyNkiWjITyxDTKXp++WL2fpGiPeM2baRm0cI/0=",
"objectType":"System.UserRole"}, ...],"changes":{},"commits":[],
"committedObjectsOmitted":false,deletes":[],"newpersistable":[],objects":[],
"resets":{}}
Aanpassen Account.FullName
Commando voor het ophalen van het eigen account en aanpassen van de volledige naam:
mx.data.get({xpath: "//Administration.Account[Name='RDE']", callback:
function(objs) {
objs[0].set("FullName", "Nieuwe naam");
mx.data.commit({mxobj: objs[0], callback: function(){}});
mx.data.get({xpath: "//System.User[Name='RDE']", callback: function(objs){}});
}
});
Request body ophalen account:
{"action":"retrieve_by_xpath","params":{
"xpath":"//Administration.Account[Name='RDE']","schema":{},"count":false},
"profiledata":{"1653063552337-36":105,"1653063552388-37":107}}
Response body ophalen account:
{"mxobjects":[{"attributes":{"Name":{"value":"RDE"},
"System.changedBy":{"value":"38562071809360074","readonly":true},
"IsAnonymous":{"value":false,"readonly":true},
"System.User_TimeZone":{"value":"34339947158700290"},
"WebServiceUser":{"value":false,"readonly":true,
"LastLogin":{"value":1653061381000,"readonly":true},"Blocked":{"value":false},
"System.UserRoles":{"value":["2533274790395906","2533274790396005",
"2533274790396105","2533274790396106","2533274790396108","2533274790396110"]},
"System.User_Language":{"value":"25051272927248385"},
"FullName":{"value":"Rob Dekkers"},
"changedDate":{"value":1653063497935,"readonly":true},"Active":{"value":true},
"createdDate":{"value":1528285702017,"readonly":true}},
"guid":"38562071809360074","hash":"VNKe0wbeQLAFMGtH7hGBJ5DpXxOXo/ivXUXMPxhH9Jc=",
"objectType":"Administration.Account"}],"changes":{},"commits":[],
"committedObjectsOmitted":false,"deletes":[],"newpersistable":[],"objects":[],
"resets":{}}
Request body commit:
{"action":"commit","params":{"guids":["38562071809360074"]},
"changes":{"38562071809360074":{
"FullName":{"value":"Nieuwe naam"}}},"objects":[],"context":[],
"profiledata":{"1653064538330-50":60}}
Response body commit:
{"changes":{},"commits":["38562071809360074"],"committedObjectsOmitted":false,
"deletes":[],"newpersistable":[],"objects":[{"attributes":{"Name":{"value":"RDE"},
...
"FullName":{"value":"Nieuwe naam"},
...
"objectType":"Administration.Account"}],
"resets":{"38562071809360074":["FullName"]}}
Request body:
{"action":"retrieve_by_xpath","params":{"xpath":"//System.User[Name='RDE']",
"schema":{},"count":false}}
Response body:
{"mxobjects":[{"attributes":{"Name":{"value":"RDE"},
...
"FullName":{"value":"Nieuwe naam"},
...
"guid":"38562071809360074","objectType":"Administration.Account"}],"changes":{},
"commits":[],"committedObjectsOmitted":false,"deletes":[],"newpersistable":[],
"objects":[],"resets":{}}
Aanroepen Administration.RetrieveTimeZones
Commando voor het aanroepen van een microflow:
mx.data.action({
params: {actionname: "Administration.RetrieveTimeZones"},
callback: function(){}
});
Request body aanroep microflow zonder parameters:
{"action":"executeaction","params":{
"actionname":"Administration.RetrieveTimeZones"},"changes":{},"objects":[],
"context":[],"profiledata":{"1653065881094-58":42}}
Response body:
{"changes":{},"commits":[],"committedObjectsOmitted":false,"deletes":[],
"newpersistable":[],"objects":[],"resets":{},"actionResult":[
{"attributes":{"Description":{"value":"(GMT-11:00) Midway/Pacific",
"readonly":true}, "RawOffset":{"value":"-39600000","readonly":true},
"Code":{"value":"Pacific/Midway","readonly":true}},"guid":"34339947158700033",
"hash":"LRCQBNS3X9AloAwdOYg11Ji0saibjCyrGskfuTn11GU=",
"objectType":"System.TimeZone"}, ...]}
Conclusie
Het is dus vrij eenvoudig om buiten schermen om via de client API met de server te communiceren en data op te vragen en te wijzigen of om microflows aan te roepen. De metadata van alle entiteiten zijn in de client API op te vragen, dus overzicht over de domain models van de app kan eenvoudig worden verkregen. Er is geen metadata van microflows of nanoflows beschikbaar. Die informatie kun je alleen vergaren door het internetverkeer tussen client en server te sniffen.
Met de client API zul je terdege rekening moeten houden om veilige apps te ontwikkelen. In de volgende blogs zullen we mogelijkheden bespreken en ideeën uitwerken om een app veilig te maken. Dit vergt denkwerk, architectuurkeuzes en extra ontwikkelinspanning ten opzichte van wat Mendix standaard te bieden heeft. We beginnen met de autorisatie in Mendix.