Role and permission custom Model - spring

I have a custom Model of Autorization that i want to represent with spring security :
I have the concpt of roles and permissions :
#Entity
public class User
...............
#ManyToMany
#Column
private Set<Role> roles;
#ManyToMany
#Column
private Set<Permission> permissions;
}
In my custom UserdetailsService i have a clean way to load the roles but i on't find any way and any componenent in spring-security related to the permissions :
public class BELUserDetailService implements UserDetailsService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User connectedUser = userRepositoy.findUserByUsername(username);
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (Role r :connectedUser.getRoles()) {
authorities.add(new SimpleGrantedAuthority(r.getRoleAWB().name()));
}
BELUserDetails belUserDetails = new BELUserDetails(connectedUser.getIdUser(), authorities);
.....
....
}
}
My roles are :
ADMIN
NORMAL USER
TRANSACTION USER
My Permissions are :
VALIDATE_TRANSATION
INIT_TRANSACTION
And the functional use case is if you want Validate a Transaction you have to have the ROLE TRANSACTION USER and the permission VALIDATE_TRANSACTION.
hasRole("ROLE_TRANSACTION_USER") and hasPermission("VALIDATE_TRANSACTION")
Another important case is that i want in the future use PermissionEvaluator to put some limits when trying to validate a transaction if the user has the Role "ROLE_TRANSACTION_USER" and the permission VALIDATE_TRANSACTION , he must also have a Amountlimit greater than the amount of the transaction and this functionality is very cool with PermissionEvaluator
That's why i need to implements both Role and permission Concepts
How i will add my permission to the standard flow of spring-security .
Thanks in advance .

By default you have only authorities in Spring Security. Just add all your roles and permissions into authorities collection. Then you can do:
hasRole("ROLE_TRANSACTION_USER") and hasRole("VALIDATE_TRANSACTION")
In most cases mixing the two is not a problem.
You have permissions in Spring Security ACL module, but you need ACL only if you want to have different security rules per domain object.
EDIT. I think the most easy way to do some additional security checks is to use SpEL. Example:
#PreAuthorize("hasRole('ROLE_TRANSACTION_USER')
and hasRole('VALIDATE_TRANSACTION')
and #amountValidatorServiceBean.isAmountValidForCurrentUser(#amount)")
public void doTransaction(Integer amount, ...)

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();
}

What does CascadeType.MERGE actually do?

I got the UserDetails entity form database and passed it as model attribute (MVC). Later, I tried to update the updated UserDetails entity in database.
Now I am getting this error.
My doubts are
What is the use case of CascadeType.MERGE
How Hibernate handles entities
How can I update existing entity which is associated with another entity.
Multiple representations of the same entity [com.rajath.instagram.entity.UserDetails#1] are being merged. Managed: [com.rajath.instagram.entity.UserDetails#637bce04]; Detached: [com.rajath.instagram.entity.UserDetails#2b6247fa]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [com.rajath.instagram.entity.UserDetails#1] are being merged. Managed: [com.rajath.instagram.entity.UserDetails#637bce04]; Detached: [com.rajath.instagram.entity.UserDetails#2b6247fa]] with root cause
java.lang.IllegalStateException: Multiple representations of the same entity [com.rajath.instagram.entity.UserDetails#1] are being merged. Managed: [com.rajath.instagram.entity.UserDetails#637bce04]; Detached: [com.rajath.instagram.entity.UserDetails#2b6247fa]
Controller Layer:
#PostMapping("/addUserDetails")
public String adduserDetails(#ModelAttribute("user") UserDetails userDetails) {
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
regisrationService.updateUserDetails(user.getUsername(), userDetails);
return "redirect:/register/addProfilePhoto";
}
Service Layer:
#Override
#Transactional
public void updateUserDetails(String username, UserDetails userDetails) {
Optional<InstagramUser> opt = instagramUserJpaDao.findById(username);
InstagramUser user = opt.get();
userDetails.setUser(user);
userDetailsJpaDao.saveAndFlush(userDetails);
}
Dao Layer:
#Override
public void updateUserDetails(String username, UserDetails userDetails) {
Session session = manager.unwrap(Session.class);
InstagramUser user = session.get(InstagramUser.class, username);
userDetails.setUser(user);
user.setUserDetails(userDetails);
session.saveOrUpdate(userDetails);
}
My entities are:
Instagramuser
// From user details table
#OneToOne(mappedBy="user", fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private UserDetails userDetails;
UserDetails
// From user table, #OneToOne matching because, only one user details entry for a user
#OneToOne(fetch=FetchType.EAGER,
cascade= {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name="username")
private InstagramUser user;
The problem with your code is likely that InstagramUser.userDetails is not really lazy, so the current UserDetails also gets loaded into the context, hence the error. Try adding optional = false to the mapping to see if the problem goes away. (See the last paragraph of my answer for why this is not the correct solution)
Alternatively, you can try to reorder your code in the following way:
UserDetails merged = userDetailsJpaDao.save(userDetails); // I am assuming userDetails.user is null at this point
instagramUserJpaDao.findById(username)
.ifPresent(instagramUser -> merged.setUser(instagramUser));
Also note that, since in your original code InstagramUser.user is the inverse side of the association, the line user.setUserDetails(userDetails); really doesn't do anything. You need to populate UserDetails.user instead to establish an association between the two entities.

Spring Data REST - prevent property edit based on role

I use Spring Boot with Spring Data REST for data access and Spring Security for access restriction.
Assume I've got simple entity:
#Entity
public class Person {
#Id #GeneratedValue
private Long id;
private String firstName;
private Boolean isAuthorizedForClasifiedData;
}
I've got two roles in the application: USER and ADMIN.
Is there any simple way to prevent USER from changing value of isAuthorizedForClasifiedData while allowing ADMIN to update it?
The only solution I came up with is writing your own setter method.
public void setIsAuthorizedForClasifiedData(Boolean isAuthorizedForClasifiedData) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Optional<? extends GrantedAuthority> role_admin = authentication.getAuthorities().stream().filter(role -> role.getAuthority().equals("ROLE_ADMIN")).findAny();
role_admin.orElseThrow(() -> new YourOwnForbiddenException());
this.test = test;
}

Why Spring security is based in username instead of user id?

In spring security, UserDetails uses username as an identifier of user. My user model have multiple types of identifications and have multiple ways of authentication (via email, phone number, facebook, etc).
I am using AuthorizationServerEndpointsConfigurer to configure my oauth2 endpoints. JdbcTokenStore stores user_name in database, and what I need to store is userId. I already changed the JdbcTokenStore to fill my needs, but I am not understanding why spring framework makes the assumption that the username is the unique identifier, my app does not even use usernames.
For example, in TokenStore inferface findTokensByClientIdAndUserName is based in username, why not findTokensByClientIdAndUserId? If I authenticate the user with email or phone number, how can I get all tokens for that user?
My user model example: id, email, phone number, facebook_id, name. Email, phone number and facebook id are optional, only one of them are required. In this case what is the best candidate for username? Should I use user id as an username in spring way of abstraction?
Not sure if I understood the whole question but will try to give my best explanation for UserDetailService. UserDetailsService's method loadByUsername is just a method which allows you to retrieve the user from your database and compare it with the credentials from the request from the client to see if it is an existing user or non existing one. Although the name of the method is loadUserByUsername, it doesn't mean that you must literally pass a username, you can pass it any argument like email, phone number or whatever you authentication is based on.
Here is an example implementation for that service, where I identify my users by calling the database through UserRepository and giving it as an argument email.
#Service
public class CustomUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
#Autowired
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Here I am retrieving the user from my database where the primary key is
// email
User user = userRepository.findByEmail(username);
if(user == null) {
throw new UsernameNotFoundException("Username not found!");
}
CustomUserDetails customUserDetails = new CustomUserDetails(user);
return customUserDetails;
}
}

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.

Resources