Annotations needed to validate request data from set of values - spring

Is there any annotation provided by spring to validate the input values from predefined set ?
Requirement validate roles assign to user, and Role value should be either user, admin, moderator.
Can I validate at the time when request comes in controller ?

You could use the Enum type to define the expected constants:
Role {
USER,
ADMIN,
MODERATOR
}
And then, your request would be:
public class User implements Serializable {
#NotNull(message = "role cannot be null")
private Role role;
}

Related

How should I design endpoint to return different models depending on User role in Spring

So lets assume I have 2 model classes - User and Presentation, and they look something like this:
#Entity
public class Presentation {
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
}
#Entity
public class User implements UserDetails {
#Id
private Long id;
private String username;
private String name;
private String surname;
private String password;
#Enumerated(EnumType.STRING)
private Role role;
}
As you can see I have a unidirectional mapping for user in Presentation class. My endpoint looks something like this:
#RestController
public class PresentationController {
#GetMapping("/{presentationId}")
public PresentationDTO getPresentation(#PathVariable Long presentationId) {
return presentationService.getPresentationById(presentationId);
}
#GetMapping
public List<PresentationDTO> getAllPresentations() {
return presentationService.getAllPresentations();
}
}
Now for my question - how do I change getAllPresentations() to return the presentations that the users with role "user" own, and return all presentations for users with role "admin"? I know I can create a separate endpoint with a different mapping (like /admin/presentations) and add #PreAuthorize(hasRole("admin")), but here is the tricky part.
For the getAllPresentations() endpoint which everyone who is authenticated is supposed to fetch his own presentations, how do I know for which user I have to return his presentations? Maybe I can get the username as a parameter but that might be dangerous cause he can submit any username he wants and get the presentations for that user. I don't know too much about Spring Security and I don't even know the right question to ask google to get an answer so I'm stuck...
Any help will be appreciated, thanks!
You don't have to pass username to your controller method. The currently authenticated user is available through a number of different mechanisms in Spring.The simplest way to retrieve the currently authenticated principal is via a static call to the SecurityContextHolder like this :
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
So you can refactor you method getAllPresentations() in service class to accept username as argument, and then you can load user by passed username and return presentations for that user.
One way to do what you want is to use #PostFilter annotation to filter List<Presentation> that the authenticated user owns or if the authenticated user has a role ADMIN like this:
#GetMapping
#PostFilter("filterObject.user.username == authentication.principal.username or hasRole('ADMIN')")
public List<PresentationDTO> getAllPresentations() {
return presentationService.getAllPresentations();
}

How to do a complicated #PreAuthorize check for an ID that is contained in child property?

According to this great post Spring-Boot #PreAuthorize allow operation only for admin or if the authenticated user id is same as path parameter id
it is shown that you can match the URL id against the user id using:
#PreAuthorize("hasRole('ROLE_ADMIN) or #authUser.id == #userId")
but what if I need to check if userId matches a child of authUser?
So suppose I have entity:
public class User {
int id;
#OneToMany(mappedBy = "owner")
Set<Store> stores;
}
and I want to validate based on #userId == #authUser.ANY_ONE_OF_THE_STORES.id?
You can create the component, for example
#Component
public class AuthComponent {
public boolean hasPermission(User user, Long id) {
// do whatever checks you want here
return someResult;
}
}
And then you can access the component method in SPEL like this
#PreAuthorize("#authComponent.hasPermission(#authUser, #userId)")
Since SPEL supports bean reference via # docs

Inherit entities

How to do when getting the user entity in Spring Boot, depending on the role that is the property of the user entity to get the entity that extend the user. For example, if user.role = "admin" gets an object of type Admin, which inherits User.
Use https://docs.oracle.com/javaee/5/api/javax/persistence/DiscriminatorValue.html for that.
In example:
#Entity
#DiscriminatorColumn(name="ROLE", discriminatorType=STRING, length=20)
#DiscriminatorValue("user")
public class User{ ... }
#Entity
#DiscriminatorValue("admin")
public class Admin extends User { ... }
If you use a EntityManager and assuming the User having the ID 1 is a admin, this might work:
Admin admin = (Admin) em.find(User.class, 1);

What's the best way to get other properties of the logged in user in Java Spring?

So, by using the line below we can extract the username and password:
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
But I would like to get the userId of the logged in user, a property of my custom User class. so I decided to do this:
CustomUserDetails details = new CustomUserDetails();
...
details.setUser(current);
UserDetails myUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
BeanUtils.copyProperties(details, myUserDetails);
CustomUserDetails myDetails = details.downCast(myUserDetails);
Integer userId = myDetails.getUser().getId(); //Fetch the custom property in User class
user.setId(userId);
However, I am just getting the user through HQL and putting it inside a temporary User class, which is pointless.
So, what's the best way to get the property userId? It seems I am better off just using a HQL query (get the User through HQL with the username of the logged in user).
I would extend the Spring Security UserDetails class and add whatever appropriate attributes that are of interest your application needs. We typically do this specifically for immutable information which includes userId.
Then when you need the appropriate attribute:
// Get the principal from spring security
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
// verify principal if your custom user details type.
// if so, get the userid and do whatever.
if(principal != null && principal instanceof CustomUserDetails) {
CustomUserDetails userDetails = CustomUserDetails.class.cast(principal);
Long userId = userDetails.getUserId();
/* do whatever you want with user id */
}
Then during authentication, just create your own CustomUserDetails object when your implementation of UserDetailsService returns the UserDetails.

Restrict access to spring REST data based on credentials

I have a CrudRepository throug which I can access my entities. Let's say I have an entity called Report (all oversimplified and not compiling):
#Entity
public class Report{
#Id
private Long id;
private boolean classified;
private Date date;
private String reportdata;
}
And a CrudRepository:
#RepositoryRestResource(collectionResourceRel = "reports", path = "report")
public interface ReportRepository extends CrudRepository<Report, Long>
{
findByDate(Date date); // <---- I want this to return only reports which are not classified for users who do not have the appropriate role
}
The findByDate will return all reports, including all classified reports for all users making the request. I want to restrict the access to the data based on the currently authenticated user. Is this possible?
You need Spring Security 4. It now integrates with Spring Data.
http://docs.spring.io/spring-security/site/docs/4.0.2.RELEASE/reference/htmlsingle/#data
Something like:
#Repository
public interface ReportRepository extends CrudRepository<Report,Long> {
#Query("select r from Report r where r.date=?1 and r.owner.id = ?#{ principal?.id }")
Report findByDate(Date date);
}
REST is stateless. It means that the server stores NO runtime informations (session, role etc.) about client. So if you want to use REST you should generate an API key for you client. Use a simple path filter to check whether the API key valid or not.
But perhaps you mean AJAX ?

Resources