I am trying to edit the Roles of an user using html and thymeleaf. The point is that everything works ok, I can edit the username, the password, etc but when it comes to the roles, I get null when saving the List. I can't find anything useful on the internet and I'm struggling with this for a couple of weeks now, I need a way to edit those roles, so the page will return a List containing those roles that are checked.
Here I have the UserDetails:
#Getter
#Setter
public class MyUserDetails implements UserDetails {
private final User user;
public MyUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream()
.map(role->new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
public int getId(){return this.user.getId();}
public User getUser(){
return this.user;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.isEnabled();
}
}
Here is the User:
#Builder
#Getter
#Setter
#Entity
#Table(name="user",schema = "public")
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#Column(name="user_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotEmpty(message = "You must provide a username!")
#Size(min=5, max=30)
private String username;
#NotEmpty(message = "You must provide a password!")
#ValidPassword
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name="user_roles",
joinColumns = #JoinColumn(name="user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private List<Role> roles;
#JoinColumn(name="last_played")
private LocalDate lastPlayed;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public List<String> getRoleNames(){
return roles.stream()
.map(Role::getName)
.collect(Collectors.toList());
}
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Reward> rewards;
#Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", enabled=" + enabled +
", roles=" + roles +
", lastPlayed=" + lastPlayed +
", rewards=" + rewards +
'}';
}
}
Here is the Role class:
#Getter
#Setter
#Entity
#Table(name="role",schema = "public")
public class Role {
#Id
#Column(name="role_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
}
The controller:
#Controller
#RequiredArgsConstructor
public class EditUserController {
private final IUserDetailsService userService;
private final RoleService roleService;
#PostMapping(USERS_SAVE)
public String saveEdit( #ModelAttribute(AttributeNames.USER) User user,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
return EDIT_USER;
}
userService.updateUser(user);
return REDIRECT+ USERS;
}
#GetMapping("/users/{id}")
public String editUser(#PathVariable int id, Model model){
List<Role> roleList = roleService.findAll();
UserDetails user = userService.findUserById(id);
model.addAttribute(AttributeNames.USER, user);
model.addAttribute(AttributeNames.ROLE_LIST, roleList);
return EDIT_USER;
}
#DeleteMapping(Mappings.USERS_DELETE_ID)
public String deleteUser(#PathVariable int id){
userService.deleteUser(id);
return REDIRECT+USERS;
}
}
And finally the html part for the roles :
<div class="form-group row">
<label class="col-form-label col-4">Roles</label>
<div class="col-8">
<th:block th:each="role:${roleList}">
<input type="checkbox"
name="roles"
th:field="*{user.roles}"
th:value="${role.id}"
th:text="${role.name}" class="m-2"/>
</th:block>
</div>
</div>
Here is a picture with the form, this user has both roles of user and admin, and they are both checked when entering the page, but if I go for the update, the roles will be modified to null.
Can you help me figure this out?
https://i.stack.imgur.com/DWpRw.png
The mapping does not need to be many to many, Use a uni directional as this:
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "role_id",
referencedColumnName = "id"
))
private List<Role> roles;
Because a role does not need to have references for corresponding users it's being used by. Then have the table created 'user_role'. After that if you change roles and save the user it should be updated in user_role table.
UPDATE
Could you try this when returning user:
#GetMapping(path = "/test")
#ResponseStatus(HttpStatus.OK)
public User test() {
User user = // get user here by id
//set roles there
return user;
}
Apparently my problem was that I was adding the MyUserInterface class to the Model
insted Of User. So in the Controller I added this:
MyUserDetails userDetails=userService.findUserById(id);
User user = userDetails.getUser();
And now thymeleaf can get access to the list of the User directly.
<th:block th:each="role:${roleList}">
<input type="checkbox"
name="roles"
th:field="*{roles}"
th:value="${role.id}"
th:text="${role.name}" class="m-2"/>
</th:block>
Related
what is the best way which can I add to the following method in a spring boot controller class to allow the user just delete the restaurant he created .
I don't want to add the user id to the path, I want the logged in user to not allowed deleting restaurant which he didn't create.
Note that I extend Auditable to add createdBy to the database mysql
#DeleteMapping("/restaurant/{restaurantId}")
public String deleteRestaurantById(#PathVariable("restaurantId") Long restaurantId) {
if(restaurantService.existsById(id) == false){
logger.info("Error occurred because this restaurant is not found!");
throw new InternalServerErrorException("There is no restaurant with this id");
}
restaurantService.deleteById(id);
return "deleted";
}
Restaurant.java
#Entity
#NoArgsConstructor
#RequiredArgsConstructor
#Getter
#Setter
#ToString
public class Restaurant extends Auditable{
#Id
#GeneratedValue
private long id;
#NonNull
#NotEmpty(message = "The restaurant must have a name")
private String name;
....
}
User.java
#Entity
#RequiredArgsConstructor
#Getter
#Setter
#ToString
#NoArgsConstructor
#PasswordMatch
public class User implements UserDetails {
#Id #GeneratedValue
private Long id;
#NonNull
#Size(min = 8, max = 20)
#Column(nullable = false, unique = true)
private String email;
#NonNull
#Column(length = 100)
private String password;
#Transient
#NotEmpty(message = "Please enter Password Confirmation.")
private String confirmPassword;
#NonNull
#Column(nullable = false)
private boolean enabled;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id",referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id",referencedColumnName = "id")
)
private Set<Role> roles = new HashSet<>();
#NonNull
#NotEmpty(message = "You must enter First Name.")
private String firstName;
#NonNull
#NotEmpty(message = "You must enter Last Name.")
private String lastName;
#Transient
#Setter(AccessLevel.NONE)
private String fullName;
#NonNull
#NotEmpty(message = "Please enter alias.")
#Column(nullable = false, unique = true)
private String alias;
private String activationCode;
public String getFullName() {
return firstName + " " + lastName;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
}
public void addRole(Role role) {
roles.add(role);
}
public void addRoles(Set<Role> roles) {
roles.forEach(this::addRole);
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
You can implement One-to-One relationship between User and Restaurant with join table if User is able to create the only Restaurant.
it is impossible for spring security to magically know what restaurants that are created by a user.
There has to be some form of relationship between the user and the restaurant.
You can either as mentioned before have a relationship between created restaurants and the user that created them.
Or you can have a column named created_by in the restaurant entity and then store the users id in that column. So that when you fetch restaurants you use the id in the Principal to filter on restaurants created by said user.
But expecting spring security to solve this for you is not going to happen.
im trying to secure my endpoints with role based access control. I have implemented the whole structure as well as CustomUserDetailService, however im not sure how should i enforce these rules on enpoints, i was looking for some nice annotation based evaluation like #PreAuthorize(hasRole('role')). My structure looks follwoing:
Permission:
#Entity
public class Permission implements GrantedAuthority {
#Id
private Long id;
#Column(name = "NAME")
private String name;
#ManyToMany(mappedBy = "permissions", fetch = FetchType.LAZY)
private Collection<Role> roles;
#Override
public String getAuthority() {
return name;
}
Role:
#Entity
public class Role implements GrantedAuthority {
#Id #Column(name="ID" )
private Long id;
#Column(name="NAME", nullable=false , unique=false)
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "role_x_permission",
joinColumns = #JoinColumn(
name = "role_id"),
inverseJoinColumns = #JoinColumn(
name = "permission_id"))
private List<Permission> permissions;
#Override
public String getAuthority() {
return name;
}
User:
#Entity(name = "User")
#Table(name = "USERS")
#Data
public class User {
#Id
private Long id;
#Column(name="LOGIN" , nullable=true , unique=false)
private String login;
#Column(name="PASSWORD" , nullable=false , unique=false)
private String password;
#ManyToMany( fetch = FetchType.LAZY)
#JoinTable(name = "USER_ROLES",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
Now i have defined my CustomUserDetailsService:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User applicationUser = userRepository.findByUsername(username);
if (applicationUser.getId() == null) {
throw new UsernameNotFoundException(username);
}
return new org.springframework.security.core.userdetails.User(applicationUser.getLogin(), applicationUser.getPassword(),
getAuthorities(applicationUser.getRoles()));
}
#Transactional
public Collection<? extends GrantedAuthority> getUserAuthorities(String username) {
User user = userRepository.findByUsername(username);
return getAuthorities(user.getRoles());
}
private Collection<? extends GrantedAuthority> getAuthorities(
Collection<Role> roles) {
return getGrantedAuthorities(getPermissions(roles));
}
private List<String> getPermissions(Collection<Role> roles) {
List<String> permissions = new ArrayList<>();
List<Permission> collection = new ArrayList<>();
for (Role role : roles) {
collection.addAll(role.getPermissions());
}
for (Permission item : collection) {
permissions.add(item.getName());
}
return permissions;
}
private List<GrantedAuthority> getGrantedAuthorities(List<String> permissions) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String permission : permissions) {
authorities.add(new SimpleGrantedAuthority(permission));
}
return authorities;
}
}
Then i'm trying to annotate my endpoint with #PreAuthorize
#PostMapping("/doSomething")
#PreAuthorize("hasRole('doSomething')")
public SomeEntity createComment(#RequestBody SomeEntity something) {
...
}
I have user with role of USER, this role doesn't have permission to doSomething, however it seems like #PreAuthorize("hasRole('doSomething')") is not working. I'm not sure what i have done wrong, coule you please point my mistake?
Also, since im using RBAC this hasRole is very missleading since access is permission based, not role based.
What would be correct way to authorize access to endpoint with RBAC approach?
You should use hasAuthority('doSomething') instead of hasRole('doSomething').
Role is just a permission with a prefix Role_.
So hasRole('XXX') is same as hasAuthority('ROLE_XXX')
I've recently started using Spring Boot (I mostly come from a python/flask and node background) with JPA and thymeleaf and I'm trying to create a Project in my database. Everything was going well, I was able to add, delete, etc.. Projects.
I added a variable Set users to my Project entity which looks like this:
#Entity
#Table(name = "project")
public class Project {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "project_id")
private int id;
#Column(name = "title")
#NotEmpty(message = "*Please provide a title")
private String title;
#Column(name = "description")
private String description;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "project_lead", joinColumns = #JoinColumn(name = "project_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
private User projectLead;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "project_user", joinColumns = #JoinColumn(name = "project_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
private Set<User> users;
...
}
The user class looks like this:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "email")
#Email(message = "*Please provide a valid Email")
#NotEmpty(message = "*Please provide an email")
private String email;
#Column(name = "username")
#NotEmpty(message = "*Please provide a username")
private String username;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#NotEmpty(message = "*Please provide your password")
#Transient
private String password;
#Column(name = "enabled")
private int enabled;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
...
}
I'm able to create new projects when I don't specify the users for the project. But I'm having trouble creating a project when I specify the users using a multiple select. In my form I have:
<form th:action="#{/secure/projects}" th:object="${project}" method="POST">
<div class="form-group">
<label th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="validation-message"></label>
<input type="text" class="form-control" th:field="*{title}" id="title" th:placeholder="Title" />
</div>
<div class="form-group">
<input type="text" class="form-control" th:field="*{description}" id="description" th:placeholder="Description" />
</div>
<div class="form-group">
<select th:field="*{users}" class="users-select form-control" multiple="multiple">
<option th:each="user : ${allUsers}" th:value="${user}" th:text="${user.username}"></option>
</select>
</div>
<button name="Submit" value="Submit" type="Submit" th:text="Create"></button>
</form>
I keep getting 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users'' for the th:field="*{users}". I don't understand why th:value="${user}" is being considered as a String when it's supposed to be of class User. Is there a way for me to simply get the results of the select, loop through it and manually add it in the controller to my object project?
My controller looks like this:
#RequestMapping(value = "/projects", method=RequestMethod.GET)
public ModelAndView showProjectForm() {
ModelAndView modelAndView = new ModelAndView();
// Get authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findByEmail(auth.getName());
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("project", new Project());
modelAndView.addObject("allUsers", userService.findAll());
modelAndView.setViewName("project_creation");
return modelAndView;
}
#RequestMapping(value = "/projects", method=RequestMethod.POST)
public ModelAndView processProjectForm(#Valid Project project, BindingResult bindingResult) {
ModelAndView modelAndView = new ModelAndView();
// Get authenticated user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findByEmail(auth.getName());
// Get all projects
List<Project> allProjects = projectService.findAll();
// Check if project already exists
Project projectExist = projectService.findByTitle(project.getTitle());
if(projectExist != null) {
bindingResult
.rejectValue("title", "error.project",
"There is already a project with this title");
}
// Get all users
List<User> allUsers = userService.findAll();
if(bindingResult.hasErrors()) {
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("project", new Project());
modelAndView.addObject("allUsers", allUsers);
modelAndView.setViewName("project_creation");
} else {
// Create project
project.setProjectLead(user);
projectService.saveProject(project);
modelAndView.addObject("userName", "Welcome " + user.getUsername() + " (" + user.getEmail() + ")");
modelAndView.addObject("success", "Project successfully created!");
modelAndView.addObject("project", new Project());
modelAndView.addObject("projects", allProjects);
modelAndView.setViewName("redirect:/secure/dashboard");
}
return modelAndView;
}
I was able to fix the 'Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'users''. I simply had to add a converter class telling how to convert from string to User object.
#Component
public class StringToUser implements Converter<String, User> {
#Autowired
private UserService userService;
#Override
public User convert(String arg0) {
Integer id = new Integer(arg0);
return userService.findOne(id);
}
}
And changed the option in my form to have a value of user.id instead of a user object. The converter would take care of the rest:
<option th:each="user : ${allUsers}" th:value="${user.getId()}" th:text="${user.getUsername()}"></option>
I have this domain class in Spring:
#Entity
#Table(name="Like")
public class Like {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToOne(cascade= CascadeType.MERGE, targetEntity = User.class)
#JoinColumn(name = "user_id")
#OnDelete(action= OnDeleteAction.CASCADE)
Set<User> user;
#OneToMany(mappedBy = "like", orphanRemoval = true ,cascade= CascadeType.ALL, targetEntity = Picture.class)
#OnDelete(action= OnDeleteAction.CASCADE)
Set<Picture> pictures;
public Like() {
}
public Like(Set<User> user) {
this.user = user;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonIgnore
public Set<User> getUser() {
return user;
}
#JsonIgnore
public void setUser(Set<User> user) {
this.user = user;
}
#JsonIgnore
public Set<Picture> getPictures() {
return pictures;
}
#JsonIgnore
public void setPictures(Set<Picture> pictures) {
this.pictures = pictures;
}
}
}
and I have this table in my sql script
CREATE TABLE IF NOT EXISTS `like` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT,
FOREIGN KEY (user_id) REFERENCES `user`(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
And this is a problem when I send post request in postman:
java.lang.IllegalArgumentException: Can not set java.lang.Long field
com.nyters.webapp.domain.User.id to java.util.HashSet
ControllerLike.java
#RestController
#RequestMapping("api/like")
public class LikeController {
private LikeService likeService;
#Autowired
public LikeController(LikeService likeService){
this.likeService = likeService;
}
#RequestMapping(path = "/{id}", method = RequestMethod.GET)
public ResponseEntity<LikeDTO> findOne(#PathVariable Long id) {
LikeDTO pictureDTO = likeService.findOne(id);
if (pictureDTO != null) {
return new ResponseEntity<>(pictureDTO, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<LikeDTO>> findAll() {
List<LikeDTO> likeDTOs = likeService.findAll();
if (likeDTOs != null) {
return new ResponseEntity<>(likeDTOs, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<LikeDTO> save(#RequestBody String likeDtoString)
throws
IOException {
LikeDTO likeDTO = new ObjectMapper().readValue(likeDtoString,
LikeDTO.class);
LikeDTO saved = likeService.save(likeDTO);
if (saved != null) {
return new ResponseEntity<>(saved, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
I guess prob is with your mapping
Like.java
#Fetch(value = FetchMode.SELECT)
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "user_Id")
#JsonIgnore
private List<User> userList;
//based on user_Id u can fetch userList from DB
User.java
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_Id")
private Long user_Id;
At first #JsonIgnore make your users not accessiable for entity from json.
At second you need to change #ManyToOne to #OneToMany or #ManyToMany for filed Set<User> user or change type to User user
UPDATE
Well, ManyToOne means that in current class you have just one element (in our case User. it`s looks as following:
class Like {
// init, class and bla-bla-bla
#ManyToOne(/*properties*/)
User user; // important - not Collection, Set or something else
and in this case your user class looks as follows:
class User {
// blablabla
#OneToMany(/*properties*/
Set<Like> likes;
So, you can imagine (?) it as follows: #external_filed To current_field. I hope you understand
I have following domain mapping:
#Entity
#Table(name = "terminal_admin_role")
public class AdminRole {
#Id
#Column(name = "role_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "role")
private String role;
public AdminRole(String role) {
this.role = role;
}
public AdminRole() {
}
// get set
#Override
public String toString(){
return role;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AdminRole)) {
return false;
}
AdminRole adminRole = (AdminRole) o;
if (!role.equals(adminRole.role)) {
return false;
}
return true;
}
#Override
public int hashCode() {
return role.hashCode();
}
}
and
#Entity
#Table(name = "terminal_admin")
public class TerminalAdmin {
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
//...
}
and following code to execute:
controller:
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#ModelAttribute #Valid TerminalAdmin terminalAdmin,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
...
terminalAdmin.setCreateDate(Calendar.getInstance());
terminalAdminService.saveTerminalAdmin(terminalAdmin);
...
}
service:
#Override
#Transactional
public void saveTerminalAdmin(TerminalAdmin newAdmin) {
String rawPassword = newAdmin.getPassword();
newAdmin.setPassword(passwordEncoder.encode(newAdmin.getPassword()));
terminalAdminDao.save(newAdmin);
emailService.sendAdminCreatedEmail(rawPassword, newAdmin.getEmail(), newAdmin.getAdminRoles());
emailService.sendAdminRegisteredForAdminEmail(newAdmin);
}
dao:
#Override
#Transactional
public void save(TerminalAdmin terminalAdmin) {
sessionFactory.getCurrentSession().save(terminalAdmin);
}
After it I see that admin roles binded to user duplicated in AdminRole table in database.
What Do I wrong? I have wrote equals method.
P.S.
before saving in debug I see following values:
Because in your new TerminalAdmin, the AdminRole it is referring does not contain ID. ID is the identity of entities. It does not use equals() to identify the identity. Without IDs, Hibernate simply treat it as a new AdminRole to be persisted to DB (as you have set corresponding cascade options in TerminalAdmin)
There are some choices you may take
Change the ID of your AdminRole to the role String, or/and
Lookup the correct AdminRole by role string, and set them in your new TerminalAdmin entity, or/and
Contains the AdminRole ID in the incoming request, etc...