I am using the current version of Spring Data Rest (SDR) and Spring Security (SS) and have following entities:
User: contains a List of teams joined and another for teams managed.
Team: contains a List for members and another for admins.
What I would like to do is customize the information returned for the entities by SDR given permissions of the current User. I'm aware of Projections in SDR but I believe they're not suitable for my current problem since this should be done transparently without having the User specify the projection in the request.
Given the following:
(1) /teams/{team_id}/members
(2) /teams/{team_id}/members/{member_id}
(3) /users/{user_id}/teamsJoined
Here is what I want to implement:
Visiting (1) by a normal member of the team would return different fields than when done by an admin.
Visiting (2) would return additional fields not returned by (1)
Visiting (3):
by the user with {user_id} should return all teams.
by another member should return only the intersection of their teams.
I was thinking about maybe using AOP but I'm not really sure if it would work. What would be the best way to implement this?
I'm not sure exposing various representations of a resource (at the same uri) based on the requesting user follows the REST philosophy. You should use another uri for that.
Maybe you could split the data visible by only some kind of users from the original entity into another 'sub'entity (1-1 relation) and restrict the access to theses related resources endpoints. You can make use of #PreAuthorize and #PostFilter annotations on your repositories methods to restrict access on your resources based on the identified user.
Related
Suppose we have a M:M relationship between a User model and a Project model in a project management SaaS. Naturally we have a pivot table mapping users to projects. An entry is deleted either if the user leaves or if the project manager removes that user from the project.
Even though the final result is essentially the same (i.e., the entry is deleted), I imagine it would be better to distinguish between these two actions by defining two separate API endpoints as follows:
user leaves project
route DELETE /users/{id}/projects/{id}
action UserProjectController#destroy($id) where $id refers to the project
project manager removes user
route DELETE /projects/{id}/participants/{id}
action ProjectParticipantController#destroy($id) where $id refers to the user
Should I ignore Cruddy by Design and RESTful design principles and simply define leave() join() remove() actions instead and use verbs in the URIs? like so:
POST /projects/{id}/join
POST /projects/{id}/leave
POST /projects/{id}/participants/{id}/remove
join,leave,remove are arguably RPC rather than REST. Pivot tables etc are how the domain is implemented and are irrelevant to the caller of the API, which works at the domain level. How do the URLs map to your domain model?
If Project has one or more User why not just use
/projects/{id}/
to manage everything about Project and Participant instances?
User is in a Project but only if
/projects/{id}/participants/{id}
has been called.
FWIW,
DELETE /users/{id}/projects/{id}
looks like it's deleting the Project instance owned by a User as the Project object has participants but the User doesn't use that terminology. Perhaps it would be:
DELETE /users/{id}/projects/{id}/participants
for consistency with the domain model.
However it seems much easier to just use Project to manage Project instances. Removing a User from a Project using the User API seems a convenience that complicates the backend and doesn't really match the Project terminology. That would mean only having UserController and ProjectController classes.
You would have to consider whether a Participant id is the same as a User id and whether the pivot table handles that but that won't affect the API. The API should be an intuitive representation of your domain model.
Something to consider: how would you do it on the web?
Even though the final result is essentially the same (i.e., the entry is deleted), I imagine it would be better to distinguish between these two actions by defining two separate API endpoints
So on the web, you might well have two different forms that are used to "achieve the same result", but that doesn't necessarily mean that the forms should target different resources.
POST /foo
Content-Type: application/x-www-form-urlencoded
action=UnsubscribeUser&otherArguments=....
POST /foo
Content-Type: application/x-www-form-urlencoded
action=CancelProject&otherArguments=....
"One resource or two resources" isn't a question of right or wrong, but rather a question of trade-offs.
POST /projects/{id}/join
POST /projects/{id}/leave
POST /projects/{id}/participants/{id}/remove
That's also "fine"; again, it's a question of trade offs. The machines don't care that your identifiers have a verb in the URI.
They do, somewhat, care whether or not the identifier for reading is the same as the identifier for writing. See RFC 7234.
But it's perfectly reasonable to say that having semantically significant URI spellings in the access log or browser history are more important to our long term success than cache-invalidation.
Keep firmly in mind that DELETE - in the context of HTTP - does not mean the same thing as DELETE in the context of SQL; we're talking about two different name spaces with their own semantics.
HTTP DELETE is of the transfer of documents over a network domain. The fact that the implementation of your handler includes a SQL DELETE is completely irrelevant.
Relatively few resources allow the DELETE method -- its primary use is for remote authoring environments, where the user has some direction regarding its effect.
It is OK to use POST.
I'm working on a REST api for a model having the following entities:
A Team cannot exists if it has no relationships with a Course and a Student. At the beginning, I created an endpoint for the teams (API/teams) for the CRUD operations. Now I ended up moving all the CRUD operations for the teams under the following URLs:
/API/courses/{courseId}/teams
The same has been done for Machine that cannot exists without any relationships with a Team and Student, so any CRUD operation should be done to the following:
/API/courses/{courseId}/teams/{teamId}/virtual-machines
This makes sense to me, since every time I need to perform an operation on a Machine I have to verify the constraint for which the Machine is owned by a Team related to a Course. For this reason, If I continued to perform any operation on URLs like /API/teams I should have requested the course and team ids to verify those contraints in the request body.
Having said this, my CourseController invokes a VirtualMachineService for all the operations on the Machine entity. What it seems odd to me is that each signature of every method in the VirtualMachineService need to have the course and the team id to verify the above constraints. This caused to have lots of duplicated code in every method.
Are my design choices correct?
The CourseController only have to invoke the methods of VirtualMachineService and to validate the parameters coming from the requests body.
Should those constraints validation be done inside the controller or inside the service?
Neither Configuration nor Model are entities. Entities are classes from the domain (real world representations related to project, if you will), not every class you use.
REST doesn't directly care about your entity model (meaning graph) but you should follow the guidelines for specifying REST endpoints which is
different endpoint for
/courses/... and /teams/... - don't mix these. Any constraints you would like to apply are applied at the backend and have nothing to do with endpoint definitions.
Validation guide https://www.baeldung.com/spring-mvc-custom-validator
I am building a Spring Boot based application to expose a JSON REST API.
In this application I have a 1-to-many relationship: one Order has multiple Items (and one Item belongs to exactly one Order).
I would like to have the following 4 API endpoints:
GET all Orders: In this case I just want the Order itself - so excluding the associated Items
GET a single Order: get the Order itself including the associated Items
GET single Item: get a single Item including the Order it belongs to (here it does not matter whether just the ID (=primary key) of the order is included or the whole order itself
GET all Items: the all the items; the associated Order is not necessary - but it also would not hurt.
Unfortunately I am a bit lost on how to model my associations and/or controller methods that expose the API endpoints.
Do you have some hints for me?
Thanks a lot!
Your first choice should always be to resort to Software Design Patterns. When developing applications which may require remote connections (or not), there is one that should be implemented in your rest api: Data Transfer Object.
Having into account you are developing under Java/Spring Framework, you should take a look at modelmapper library and to this guide.
I have successfully done the same task in my rest api.
Not sure if there is a better method of doing that, but my approach would be to model and fetch the relations using Hibernate, but in a lazy manner (https://howtoprogramwithjava.com/hibernate-eager-vs-lazy-fetch-type/).
In your controller, you do not return the entity but a DTO class that might be pretty similar to your entity. That DTO is created by some mapper component that provides the logic of including or not including associated items, etc.
Question: Any HAL clients or examples of accessing HAL API with admin-on-rest ?
I got started because HAL was mentioned in the first paragraph of the introduction, but now I'm having trouble finding any examples or anyone else using HAL rest client, so I am winding up for now just writing a bunch of simple findAll repositories on top of the already robust existing HAL API.
Adding a more concise answer here that isn't polluted with my thought process now that I've got it all figured out (for anyone's future reference)... Again assuming the HAL API was made with Spring Data Rest.
The four major keys to this integration are:
Exposing foreign key attributes in your JPA entities, which is required in several places by admin-on-rest #Column(name="parentEntity", updatable=false, insertable=false) private Integer parentEntityId;
Exposing all your entity IDs using RepositoryRestConfiguration.exposeIdsFor( MyEntity.class )
Annotate your repositories as #RepositoryRestResource and have them extend PagingAndSortingRepository<MyEntity, Integer>, QueryDslPredicateExecutor<MyEntity> to expose extremely useful search filters by attribute name (e.g. /api/myEntitys?field1=foo&field2=bar).
When submitting create and save requests with foreign keys make sure to adjust your params.data to include the linked resource (e.g. 'http://myserver.com/api/myEntitys/19') on top of (or in place of, HAL has no use for it) the foreign key you exposed in 1. (e.g. myEntityId=19)
Other small items of note:
use PATCH instead of PUT when updating (you may be able to use PUT if you are more of a hibernate expert and can map your entities better than I can but I had trouble getting it mapped perfectly and HAL's PATCH will take partial entities)
When submitting GET_LIST and GET_MANY_REFERENCE you get the total number of items and pagination parameters from the 'page' section of the response, and you use 'size' and 'page' query params in your API requests. (so, no need for headers and stuff)
To change the default 'equals' filter for any string entries (from 3. above) to a 'contains' filter, you will have to also extend QuerydslBinderCustomizer<QMyEntity> and provide your own customize method in each of your repositories. For example:
default void customize( QuerydslBindings bindings, QChampion champion )
{
bindings.bind( String.class ).first( ( StringPath path, String value ) -> path.contains( value ) );
}
We don't have any examples for HAL specifically. However, the point of this introduction was that admin-on-rest is backend agnostic.
You can create your own custom rest client by following the documentation. Read the code of existing ones for inspiration.
For anyone referencing this in the future, if you happen to be in control of your API through Spring Data Rest you can consider the use of an excerptProjection on every one of your existing repositories that shows an inline version of your entity. This would work if there were absolutely nothing besides admin-on-rest accessing your API.
For my case I am planning on writing a custom projection for every rest resource that has entities and naming it the same thing: "inline". Then in the admin-on-rest restClient, just always asking for the inline projection on every GET_MANY or GET_MANY_REFERENCE request.
This is the best I have at the moment. It's not perfect but for the amount of entities I have it's still many weeks faster than building a CRUD interface from scratch so I highly recommend admin-on-rest.
I'm new to Spring JPA/jHipster.
My question is inspired by the jHipster talk by Julien Dubois: https://youtu.be/R3jm2qmqctI?t=43m7s
Assume you have a bank account with operations on it (+100$ for restaurant, -50$ ATM, ...)
Each bank account has an owner of course.
The payload of a POST REST call that creates the operation could look like this:
{"amount":100,"description":"restaurant","bankaccount":{"id":1136}}
The id of the bankaccount is unique and (for the sake of this example) would have been sent to me earlier via another REST call.
Unfortunately, nothing prevents a malicious user to change this value. One could simply guess the id of a bankaccount owned by someone else, and then the operation would be added to that one.
I have not (yet) seen examples that deal with this problem.
Should I explicitly check if the bankaccount is owned by the user?
I imagine that this type of verification may cascade through all your entities, causing a lot of extra calls.
Maybe I'm missing something?
Thanks,
Andy
Yes it's your reponsibility to check in your REST controller or underlying services that an operation is authorized. Spring security offers different mechanisms to do it in particular by using #PreAuthorize and #PostFilter.
It's also a good thing to use DTOs, this way you can better control what fields of your entities are exposed for reading and writing through the API.