REST URIs for a Rule Engine (stateless engine) [closed] - api

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I need some guidance for designing REST URIs for a rule engine. There are a set of rules defined and stored in the back-end system in the form of XML files. These rules are grouped into various categories and a set of rules get executed based on the category specified by the user. The user passes a set of user selections/options and category as input to the rule engine. The rule engine executes the rules belonging to the category against the user selections/options. These are process/compute like operations which doesn't involve persistence of user state into the system. On a whole, the user state is not maintained in the rule engine... ie., a stateless rule engine.
I am trying to design REST APIs for rule engine execution (rule creation part is already taken care of):
The operation doesn't Create, Update, Delete any serve side resources.
The rules are executed against the user selections.
User selections can be complex (hierarchical structure) and it can't modeled as URI params.
Request your guidance is designing a REST URI pattern considering the above mentioned aspects.
To get a rules belonging to a category:
GET /rule_category/{id}
To process user selections in the context of rules (rule execution by rule engine):
POST /rule_engine
BODY to contain a JSON structure with rule category & user selections
Please provide your suggestions on the above URI design and any other possible URIs for the above mentioned use case. Should PUT/POST be used for rule execution URI?
EDIT 1: Adding the sample JSON/XML structure that encapsulates rule category and user selection info:
{
processdata:{
rulecategory:'ruleCat1',
userselections:{
userselection:[
{
item:'us1'
},
{
item:'us2'
},
{
item:'us3',
customselection:{
value:'cs1'
}
}
]
}
}
}
<ProcessData RuleCategory="ruleCat1">
<UserSelections>
<UserSelection Item="us1"/>
<UserSelection Item="us2"/>
<UserSelection Item="us3">
<CustomSelection Value="cs1"/>
</UserSelection>
</UserSelections>

REST is not Remote Procedure Call (RPC). REST is about Resources.
If you want to have a RESTful API that handles computations, model computations as resources.
Create a new computation resource
Request
POST /computations
Content-Type: application/json
{
// the selected rules etc.
}
Response
201 Created
Location: /computations/748A9FC0-B74E-11E4-8822-4D7FDD9DA696
where 748A9FC0-B74E-11E4-8822-4D7FDD9DA696 is the ID of this computation generated by the server.
Get the state of the computation
Request
GET /computations/748A9FC0-B74E-11E4-8822-4D7FDD9DA696
Response: still computing
200 OK
Content-Type: application/json
{
"id": "748A9FC0-B74E-11E4-8822-4D7FDD9DA696",
"data": { ... },
"state": "computing"
}
Response: finished
200 OK
Content-Type: application/json
{
"id": "748A9FC0-B74E-11E4-8822-4D7FDD9DA696",
"data": { ... },
"state": "finished",
"result": {
// details about the result
}
}

Your only options is POST in this case.
Be aware that REST has constraints e.g. uniform interface constraint, which includes HATEOAS. So you have to send links to your clients, which they can follow. In this case you will have hard time to explain the (machine) clients how to assemble the body of your request. A possible way to overcome this, is defining your application specific request media type.
So it is possible to solve this with REST, but I think you will work in this case much more than you would by an RPC. I agree with Lutz, you should not use REST by your Rule Engine if you don't have other resources which state are kept on the server.

Related

IBM Domino 10 - integrating with Resource Reservations via Domino Data Services API

We're trying to integrate with IBM Domino via REST API to pull out information about reservations/events in a specific room and also be able to create new events/reservations remotely. We already integrated with other services such as Microsoft Exchange, but IBM seems to be the toughest of them all.
I studied deeply into it and read thousands of articles & stack overflow questions, and got pretty far but still can't make any real use out of it.
What I currently plan on doing is this:
Pull information about reservations from /api/data/collections/name/($Reservations) or ($Calendar)
Create events/reservations using the documents api, POSTing to /api/data/documents?form=Reservation, I already tried doing it and my reservation even showed in Domino Admin (not in Notes client though), but it had some errors (probably just some json problem on my side)
While it looks kinda clear and easy, it really isn't. I have a few questions:
How can I get reservations/calendar for a specific room? ($Calendar) returns all events in the database, not even including in which room it is, to get that information I would need to additionally query each reservation by it's unid and that would probably kill the entire app
Is there any way I could filter/search the /api/data/documents to return only documents whose form field has a value of Reservation or any other value? This way I could get all the reservation documents without querying each of the documents directly (/api/data/documents only returns the href to the document without any interesting data), I wouldn't also need to additionally enable DAS for each view I want to use.
What are the fields like $25 returned in the json, and how can I know what's their purpose if they don't have any real name? They often contain interesting data, such as the room name.
I also looked into the FreeBusy api service, and it's pretty interesting and I could easily use it to look for reservations (/busytimes) in the room I want, if it ever returned what resource/reservation is causing the busy time. It just shows the start and end time, nothing else..
I also read suggestions that one should create a 'main' user to handle the reservations and use his calendar api (/api/calendar/events), but afaik it can't be done that way.
However I tried creating events in the users calendar in specified room, and kinda got it to work by adding the following attendee in the json |(PHP syntax, actually):
'organizer' => [ 'email' => 'admin/test#test.com' ],
'attendees' => [
[
'role' => 'req-participant',
'userType' => 'room',
'status' => 'accepted',
'rsvp' => true,
'email' => 'testroom#test.com',
],
],
But it doesn't really get displayed in the room reservations, unlike normal events created in IBM Notes. It also cannot be edited or deleted in IBM Notes, and it has "Accepted: " in front of the subject, and it says "attendance is delegated for admin". To delete it, I need to delete it via API through its unid directly. x-lotus-noticetype is being set to A so I guess it's not being treated as a meeting but as an notice, no idea why though.
I'd really like some help or suggestions on how I could get this working, are there any other ways that would have any sense?
Edit:
After struggling a lot and reading Dave's reply, I think it would be a good solution to have a single user that would do the reservations via calendar api, because the direct data api probably won't work. I could just only pull the list of all reservations from Rooms database ($Calendar) or ($Reservations) view, or make some sort of my own view.
However! I cannot get the calendar method to work on my local IBM Domino server. Dave pointed to me that I need to specify a valid email (internet address) of the organizer, so I set my user's internet address to testmail#test.test (test.test is mapped to 127.0.0.1 in the hosts file). Now as soon as I try to use that address like that:
"organizer": {
"email": "testmail#test.test"
}
I cannot even create the event/reservation (through /mail/admin.nsf/api/calendar/events), it's returning 500 internal error with cserror 1026, and Domino logs
[CS API]> Error | calendarapi.c(379) : There was an error sending out notices to meeting participants. (0x8E4)
Error connecting to server test/test: The remote server is not a known TCP/IP host.
So it has a problem with sending the notice, and doesn't create the event at all. I thought it may not work with localhost, so I set my users email to an external mail service, and I even received the email, but the event was still created incorrectly (x-lotus-noticetype A is being added automatically and overrides whatever I send as the value), it's not visible in the Room Reservations database.
Here's the json object of an event created via Notes client:
"events": [
{
"href":"\/mail\/admin.nsf\/api\/calendar\/events\/2B35FABBC50EA4D0C12583BC002E26FA-Lotus_Notes_Generated",
"id":"2B35FABBC50EA4D0C12583BC002E26FA-Lotus_Notes_Generated",
"summary":"Notes client meeting",
"location":"Test room\/Test site#test",
"start": {
"date":"2019-03-13",
"time":"09:30:00",
"tzid":"Central European Standard Time"
},
"end": {
"date":"2019-03-13",
"time":"10:30:00",
"tzid":"Central European Standard Time"
},
"class":"public",
"transparency":"opaque",
"sequence":0,
"last-modified":"20190313T082436Z",
"attendees": [
{
"role":"chair",
"status":"accepted",
"rsvp":false,
"displayName":"admin\/test",
"email":"testmail#test.test"
},
{
"role":"req-participant",
"userType":"room",
"status":"needs-action",
"rsvp":true,
"displayName":"Test room\/Test site",
"email":"room#test.test"
}
],
"organizer": {
"displayName":"admin\/test",
"email":"testmail#test.test"
},
"x-lotus-broadcast": {
"data":"FALSE"
},
"x-lotus-notesversion": {
"data":"2"
},
"x-lotus-appttype": {
"data":"3"
}
}
]
As you can see, Notes is able to create the event with testmail#test.test successfully.
Now here's an event created with my API, but with admin/test#test.test as the organizer's email (because normal email doesn't let me create the event):
"events": [
{
"href":"\/mail\/admin.nsf\/api\/calendar\/events\/E1D1F752203FC2DFC12583BC002FCB12-Lotus_Auto_Generated",
"id":"E1D1F752203FC2DFC12583BC002FCB12-Lotus_Auto_Generated",
"summary":"Api reservation test",
"location":"Test room\/Test site#test\r\nCN=Test room\/O=Test site",
"description":"API Generated event\r\n",
"start": {
"date":"2019-03-20",
"time":"11:00:00",
"utc":true
},
"end": {
"date":"2019-03-20",
"time":"15:00:00",
"utc":true
},
"class":"public",
"transparency":"opaque",
"sequence":0,
"last-modified":"20190313T084201Z",
"attendees": [
{
"role":"chair",
"status":"accepted",
"rsvp":false,
"displayName":"admin\/test",
"email":"testmail#test.test"
},
{
"role":"req-participant",
"userType":"room",
"status":"needs-action",
"rsvp":true,
"displayName":"Test room\/Test site",
"email":"room#test.test"
}
],
"organizer": {
"displayName":"admin\/test",
"email":"testmail#test.test"
},
"x-lotus-broadcast": {
"data":"FALSE"
},
"x-lotus-notesversion": {
"data":"2"
},
"x-lotus-noticetype": {
"data":"A"
},
"x-lotus-appttype": {
"data":"3"
}
}
]
As you can see, the organizer's & chair emails were automatically updated by Lotus to testmail#test.test, and theorethically everything should work but it doesnt. In Notes, I see the event as 'Accepted: Api reservation test' and I cannot modify things like the room, or don't have the option to delete it from right click menu (I can delete it with Del keyboard button though)
The only difference is that x-lotus-noticetype get's added, and I don't even know why
Edit 2:
I got it to work! Dave pointed that I may have some configuration issue, so I re-installed the server & setup everything again (including the mail services), I used admin#test.test and the meeting was succesfully created & added to the room reservations. Server console only showed that the message was delivered.
HOWEVER! I was able to create as many identical meetings as I wanted, they weren't added to the reservations database but they were succesfully created in my calendar (with the room assigned to them) without any errors (not even in the sever console), this is obviously bad. Is there any way to check (externally, through API) if the reservation was created succesfully, and prevent it's creation if the room is busy at that moment? Notes client prompts an error when the room is busy. I could probably use FreeBusy api, however that would require another HTTP request before each reservation attempt, but if that's the only way then I'll just take it. I see that the status field of the attendeed room is set to declined, but the response from POST still contains needs-action so I'd need to do some delayed request once again to check if the status has changed to declined or not.
Also, while it works, I still don't know how I could obtain a list of reservations in a selected room? The already existing views in Reservations database don't give many details, and they need to be exclusively enabled DAS services in order to work. Is there any other way that could work properly?
Another thing is, is there any way I could get current user's email address to use for the reservations, or can I only 'hardcode' it manually? Same goes for room's email. Currently, I need to have:
User name
User password
User mail database (/mail/admin.nsf/)
User email
Room email
and if I'd want to read some data from the Reservations database directly, then I'd also need to have the path to that database. This isn't really user-friendly, I'd like to automate some things if possible. Otherwise the integration may be impossible to make.
The reservation database was designed to manage reservations either (1) directly through it's own UI, or (2) indirectly by auto-processing notifications from calendar users. By using the DAS data API, you are asserting you can manage reservations (3) programmatically -- by manipulating the low-level document items. You might get this to work, but I don't think the reservation database was designed with that in mind.
That's why I think this answer is the best option. It leverages auto-processing (#2 above) and saves you from dealing with the internal design of reservation documents. If you use this approach, you should give the DAS calendar API a list of attendees like this:
"attendees":[
{
"role":"req-participant",
"userType":"room",
"status":"needs-action",
"rsvp":true,
"email":"room#mycorp.com"
}
]
In other words, status must be "needs-action" -- not "accepted" as shown in your original post. Also, make sure you are using the correct email address for both the organizer and the target room. The above example shows an Internet-style address for the room, but administrators don't always give a room an Internet address.

What if access control rule defined for participant/asset contradicts access control rule for transaction?

I have a question regarding access control.
Specifically, the question is about the relationship between access control rules defined for participants or assets on the one hand and asset control rules defined for transactions accessing those participants/assets.
Here is an example:
Assume a Hyperledger Fabric network is used to create some kind of social network for employees of a company.
The following rule states that an employee has write access to his own data:
rule EmployeesHaveWriteAccessToTheirOwnData {
description: "Allow employees write access to their own data"
participant(p): "org.company.biznet.Employee"
operation: UPDATE
resource(r): "org.company.biznet.Employee"
condition: (p.getIdentifier() == r.getIdentifier())
action: ALLOW
}
Let's assume that the write access is facilitated through a transaction called "UpdateTransaction". Further assume that (maybe by accident) the action value of the access control rule of transaction "UpdateTransaction" is set to "Denied"
rule EmployeeCanSubmitTransactionsToUpdateData {
description: "Allow employees to update their data"
participant: "org.company.biznet.Employee"
operation: CREATE
resource: "org.company.biznet.UpdateTransaction"
action: Denied
}
Now there is the following situation:
Each employee is (through rule 1) given the right to change his/her data.
At the same time employees are not allowed to submit the transaction "UpdateTransaction" to change the data (see rule 2).
Is it now impossible for employees to change their data? Or are employees still able to change their data without submitting the transaction "UpdateTransaction"?
Put differently: is there a way for participants to access data (for which they have access rights) without using any of the transactions defined in the .cto-file?
I think the answer is, it depends.
In your example, denying access to the org.company.biznet.UpdateTransaction transaction would result in org.company.biznet.Employee participants being unable to use that transaction to update their data, even though they would otherwise be allowed.
Having said that, you should keep the system transactions in mind since they provide another potential route for org.company.biznet.Employee participants to update their own data.
For example, I tried that out on the basic-sample-network by replacing the EverybodyCanSubmitTransactions rule with
rule NobodyCanSubmitTransactions {
description: "Do not allow all participants to submit transactions"
participant: "org.example.basic.SampleParticipant"
operation: CREATE
resource: "org.example.basic.SampleTransaction"
action: DENY
}
That business network includes an OwnerHasFullAccessToTheirAssets rule and I was able to use the org.hyperledger.composer.system.UpdateAsset transaction to make updates for participants that owned an asset using the command,
composer transaction submit -d "$(cat txn.json)" -c party1#basic-sample-network
Where txn.json contained,
{
"$class": "org.hyperledger.composer.system.UpdateAsset",
"resources": [
{
"$class": "org.example.basic.SampleAsset",
"assetId": "ASSET1",
"owner": "resource:org.example.basic.SampleParticipant#PARTY1",
"value": "5000"
}
],
"targetRegistry": "resource:org.hyperledger.composer.system.AssetRegistry#org.example.basic.SampleAsset"
}
That wouldn't work if you had locked down the system namespace in your ACL rules though. (ACLs need a lot of thought!)
The other important thing to remember about ACLs is that they do not apply if you use the getNativeAPI method to access data via the Hyperledger Fabric APIs in your transaction processor functions.
Check out the system namespace reference along with the ACL reference, plus there is an ACL tutorial which may be of interest if you haven't seen it.

slashDB accessing a database via POST request and using APIkey yields 403 error

Question about security for POST method of HTTP:
I made a user called "MyAPP":
{
"userdef": [
"view",
"create"
],
"api_key": "dzn8k7hj2sdgddlvymfmefh1k2ddjl05",
"user_id": "MyAPP",
"name": "MyAPP",
"creator": "admin",
"edit": [],
"dbdef": [
"view",
"create"
],
"querydef": [
"view",
"create"
],
"databases": {
"Gaming": {
"dbuser": "mydbuser_here",
"dbpass": "mypass_here"
}
},
"password":
"$6$rounds=665736$x/Xp0k6Nj.5qzuM5$G.3w6Py1s.xZ83RHDU55qonNMpJe4Le8nD8PqjYKoOtgbab7T22knJPqwHspoT6BQxp.5gieLFuD0SdD9dyvi/",
"email": "",
"view": []
}
Then I wanted to issue a POST in order to execute a SQL Pass-thru
such as this:
http:///query/InsertBestScore/Score/99/ScreenName/GollyGolly.xml?apikey=dzn8k7hj2sdgddlvymfmefh1k2ddjl05
Where I built a query and named it "InsertBestScore":
insert into Gaming.Leaderboard
(ScreenName, Score)
values
(:ScreenName, :Score);
If I run this via POSTMAN using the POST method:
... then I get an access, 403 :
<?xml version="1.0" encoding="utf-8"?>
<SlashDB>
<http_code>403</http_code>
<description>Access was denied to this resource. Please log in with your username/password or resend your request with a valid API key.</description>
<url_template>/query/InsertBestScore/Score/{Score}/ScreenName/{ScreenName}.xml</url_template>
</SlashDB>
Also, I would be calling this POST (or PUT) request from an application, in my case a Python program running from within a AWS Lambda Function.
Now, I came across this in the documentation:
Two parameters API key
SlashDB also allows a two parameters credentials in this authentication method - app id and api key. This may come handy when integrating with API management systems like 3Scale. By default header and query string argument would be:
• appid - identifies certain application
• apikey - secret for the application
Request with API key in header - Access granted
... however in the example above, I don't see where the appid comes into play.
Can you tell me how one would call the SlashDB endpoint and pass a APIkey and assure that the userid is known as MyAPP.
So, to sum up, the Documentation mentions:
• Another application utilizes an API key to authenticate, which is sent with every request. The application is recognized as SlashDB user App2, which uses database login db_admin. Effectively this application can SELECT, UPDATE, INSERT and DELETE data.
So I want to actually, do just what is in that bullet: Identify myself as the user (instead of App2, I'm user MyAPP), and then use the dbuser and dbpass that was assigned to access that "Gaming" database.
Idea?
Make sure you've given user MyAPP permission to execute the query.
To do so:
login as admin,
go to Configure -> Queries,
open your query definition,
update field Execute. It accepts comma separated user ids.
OK, there are really two questions here:
Why was access denied?
What is the appid and how to use it.
Ad. 1: There are two authorization barriers that the request has to clear.
The first one is imposed by SlashDB in that the user executing the query must be listed in the Execute field on the query definition screen. This is done under Configure -> Queries -> "edit" button on your query.
The second barrier is imposed by the database. The SlashDB user who is executing your POST request must be mapped to a physical database user with INSERT privileges to the Gaming.Leaderboard table. It goes without saying that this database user must be associated with the database schema in which the table exists.
Ad. 2. To enable the appid the user api key must be composed out of two parts separated by colon ":". The first part will be interpreted as the appid and the second will be the apikey.
To do that, use Configuration -> Users -> 'edit' button for the user in question. Then simply add a colon at the beginning of the API key and type in your desired appid to the left of the colon. The app will have to supply both keys to execute the request. Note that the names of those keys (i.e. appid) are configurable in /etc/slashdb/slashdb.ini.
The reasoning behind this feature is to facilitate API Management platforms, which can help with key management, especially when API will be exposed to third party developers.

Can I include the all JSON paths required in a JSON file and use it inside my feature file for match or anything else?

For eg, I save paths.json and add the following. I know there is a mistake in the way i try, but not able to find it
{
"name": "JSON Path's",
"description": "This mainly store the paths of the JSON elements which are validated in each scenario",
"SC_4": {
"appHeader": "$.testResponse.applicationHeader",
"ProductsAction": "$.testResponse.applicationHeader.item.action"
}
}
* def attributes = read('../jsonpaths/paths.json')
Given url endPointURL
And request baseJsonRequest
When method post
Then status 200
* def appHeaderSecondTry = attributes.SC_4.appHeader
* print appHeaderSecondTry
Wow you are getting to be a power user of Karate so soon :P
Okay, personally I don't recommend trying to build a mini-framework over Karate like you are doing, I can see what you are trying to do - keep all assertions "declarative" and try to re-use common code for the actual HTTP call etc.
My opinionated take on this is, you save very few lines of code (because of how easy it is to make an HTTP request in Karate). Also, you should be able to re-use a feature file via the call keyword. I would urge you to try and keep the match statements "normal" in your tests.
That said, what you are missing is the karate.eval() operation, look for it in the doc. Here is an example (line 91): js-arrays.feature

Best practice to batch update a collection in a REST Api call [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
Hi I'm looking for best practices with API design to batch update a collection with an API call.
My collection has URL /api/v1/cars and I would like to update all the cars in the collection to add a timestamp of the current time.
{
data: [
{
manufacturer: 'Porsche',
timestamp: ...
},
{
manufacturer: 'BMW',
timestamp: ...
}
{
manufacturer: 'Peugeot',
timestamp: ...
}
}
I thought about a few options but I can't figure what is the best practice.
Should it be:
1/ Modelled as another resource such as POST api/v1/cars/updateTimestamp
2/ Passed as a query parameter: PUT api/v1/cars?updateTimestamp
3/ Pass in the body of the request:
POST api/v1/cars
{"operation":"Update timestamps"}
I'd like to emphasize that the whole processing should be done on the back-end and not passed by the client.
Same problem would happen for any complex processing that happens on the back end..
How could I keep the API RESTy in this case.
Thanks a lot for your help/any pointer to relevant ressources.
As there is no partial PUT defined in HTTP, you either need to send the whole entity for each resource to update or use some other operations.
POST
As POST is an all-purpose operation you can use it to create a short-living temporary resource at the server (which further does not even have to have an own URL). On receipt the server can update either all specified entries or all entries in general with some provided field-value combination (certain table alterering may be necessary though if the column is not yet know in relational databases)
A simple request may look like this:
POST /api/v1/cars/addAttributes HTTP/1.1
Host: example.org
Content-Length: 24
Content-Type: application/json
If-Match: "abc123"
{
"timestamp": "..."
}
This approach has the advantage that it could be sent to the server anytime, even without prior knowledge of the current state. This, however, also has the danger that certain entries get updated which shouldn't be affected. This can either be influenced by specifying an If-Match header, which points to a certain version-hash and is changed on every entity update, or by adding a certain restrictor to the JSON body which the server also has to understand.
PATCH
Similar to POST, which can literally send anything to the server, PATCH is intended for modification of resources. A single request explicitely may update multiple resources at once, however, the client needs to define the necessary steps to transform the resources from state A to state B. Therefore, the client also needs to have the latest state of the resource in order to successfully transform the resource to its newest state (-> ETag and If-Modified HTTP headers)
A JSON Patch request for the provided example may therefore look like this:
PATCH /api/v1/cars HTTP/1.1
Host: example.org
Content-Length: 215
Content-Type: application/json-patch+json
If-Match: "abc123"
[
{ "op": "add", "path": "/data/1", "value": [ "timestamp", "..." ] },
{ "op": "add", "path": "/data/2", "value": [ "timestamp", "..." ] },
{ "op": "add", "path": "/data/3", "value": [ "timestamp", "..." ] }
]
Where /data/1, /data/2 and /data/3 are the unique identifiers for the resources of Porsche, BMW and Peugeot.
PATCH specifies that a request has to be atomic which means that either all or none of the instructions succeed which also brings some transaction requirements to the table.
Discussion
As already mentioned, in POST requests you can literally send anything you want to the server at least as long as the server is capable of understanding what you are sending. It is therefore up to you how you design the request structure (and the server logic as well).
PATCH on the other hand, especially with JSON Patch defines some strict rules and predefined operations typically to traditional patching. The transaction requirement can be a burden but also a benefit. In addition to that, PATCH is not yet final and still in RFC though it is already widely available.
I would do a PUT to do an update operation. PUT is for updating objects, POST for creating. You could specify fields using query parameters.
POST /api/v1/cars > create new cars
PUT /api/v1/cars > update car based on id in the object. You could
also create a cars/:id route if you want to use the route without an
id to do an update on all car objects (which seems not to happen in
many applications).
GET /api/v1/cars > list all cars
GET /api/v1/cars/:id > list one car.
To answer your question. You want to update all car objects. In that case I would use the PUT /api/v1/cars and specify a /api/v1/cars/:id to do an update on one car, although I do not see why you want to implement this. If it is a one time operation, I would update all cars in the DB instead of creating an API route.