This post will build on the last development series that began with
Spring Thymeleaf Roles CRUD - Part 1. In this post we will create the CRUD controller and pages for Permissions. The Permissions maintenance pages will be a bit more complicated than the CRUD pages for the other entities.
1. To begin, we will update the security configuration file so that we can access the permissions pages. The additional entry is highlighted in the code below.
package com.dtr.oas.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.dtr.oas.exception.AccessDeniedExceptionHandler;
@Configuration
@EnableWebMvcSecurity
@ComponentScan(basePackageClasses=com.dtr.oas.service.UserServiceImpl.class)
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(UserDetailsService userDetailsService, AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService);
}
@Autowired
AccessDeniedExceptionHandler accessDeniedExceptionHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/error/**").permitAll()
.antMatchers("/strategy/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("ADMIN")
.antMatchers("/role/**").hasRole("ADMIN")
.antMatchers("/permission/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedExceptionHandler);
}
}
2. As with the User CRUD pages, we will need a DTO to transfer state to the controller. This is required because Permission objects holds references to one or more Role objects. Since Java objects cannot be returned from the browser, we will use a Permission DTO that will hold references to Role Id numbers, which can be returned from a browser. The DTO is shown below.
package com.dtr.oas.controller;
import java.io.Serializable;
import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
public class PermissionDTO implements Serializable {
private static final long serialVersionUID = -2707641174043923410L;
static Logger logger = LoggerFactory.getLogger(PermissionDTO.class);
private int id;
@NotNull(message = "{error.permission.permissionname.null}")
@NotEmpty(message = "{error.permission.permissionname.empty}")
@Size(max = 50, message = "{permission.permissionname.role.max}")
private String permissionname;
private List<Integer> permRoles;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPermissionname() {
return permissionname;
}
public void setPermissionname(String permissionname) {
this.permissionname = permissionname;
}
public List<Integer> getPermRoles() {
return permRoles;
}
public void setPermRoles(List<Integer> permRoles) {
this.permRoles = permRoles;
}
@Override
public String toString() {
return String.format("%s(id=%d, permissionname='%s')",
this.getClass().getSimpleName(),
this.getId(), this.getPermissionname());
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null)
return false;
if (o instanceof PermissionDTO) {
final PermissionDTO other = (PermissionDTO) o;
return Objects.equal(getId(), other.getId())
&& Objects.equal(getPermissionname(), other.getPermissionname());
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(getId(), getPermissionname());
}
}
3. The last Java file that we will create is the controller for the Permission object. The controller is shown below, with some of the more important details described after the code.
package com.dtr.oas.controller;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.dtr.oas.exception.DuplicatePermissionException;
import com.dtr.oas.exception.PermissionNotFoundException;
import com.dtr.oas.exception.RoleNotFoundException;
import com.dtr.oas.model.Permission;
import com.dtr.oas.model.Role;
import com.dtr.oas.service.PermissionService;
import com.dtr.oas.service.RoleService;
@Controller
@RequestMapping(value = "/permission")
@PreAuthorize("denyAll")
public class PermissionController {
static Logger logger = LoggerFactory.getLogger(PermissionController.class);
static String businessObject = "permission"; //used in RedirectAttributes messages
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Autowired
private MessageSource messageSource;
@ModelAttribute("allRoles")
@PreAuthorize("hasAnyRole('CTRL_PERM_LIST_GET','CTRL_PERM_EDIT_GET')")
public List<Role> getAllRoles() {
return roleService.getRoles();
}
@RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET)
@PreAuthorize("hasRole('CTRL_PERM_LIST_GET')")
public String listPermissions(Model model) {
logger.debug("IN: Permission/list-GET");
List<Permission> permissions = permissionService.getPermissions();
model.addAttribute("permissions", permissions);
// if there was an error in /add, we do not want to overwrite
// the existing user object containing the errors.
if (!model.containsAttribute("permissionDTO")) {
logger.debug("Adding PermissionDTO object to model");
PermissionDTO permissionDTO = new PermissionDTO();
model.addAttribute("permissionDTO", permissionDTO);
}
return "permission-list";
}
@RequestMapping(value = "/add", method = RequestMethod.POST)
@PreAuthorize("hasRole('CTRL_PERM_ADD_POST')")
public String addPermission(@Valid @ModelAttribute PermissionDTO permissionDTO,
BindingResult result, RedirectAttributes redirectAttrs) {
logger.debug("IN: Permission/add-POST");
logger.debug(" DTO: " + permissionDTO.toString());
if (result.hasErrors()) {
logger.debug("PermissionDTO add error: " + result.toString());
redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.permissionDTO", result);
redirectAttrs.addFlashAttribute("permissionDTO", permissionDTO);
return "redirect:/permission/list";
} else {
Permission perm = new Permission();
try {
perm = getPermission(permissionDTO);
permissionService.addPermission(perm);
String message = messageSource.getMessage("ctrl.message.success.add",
new Object[] {businessObject, perm.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("message", message);
return "redirect:/permission/list";
} catch (DuplicatePermissionException e) {
String message = messageSource.getMessage("ctrl.message.error.duplicate",
new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("error", message);
return "redirect:/permission/list";
} catch (RoleNotFoundException e) {
String message = messageSource.getMessage("ctrl.message.error.notfound",
new Object[] {"role ids", permissionDTO.getPermRoles().toString()}, Locale.US);
redirectAttrs.addFlashAttribute("error", message);
return "redirect:/permission/list";
}
}
}
@RequestMapping(value = "/edit", method = RequestMethod.POST)
@PreAuthorize("hasRole('CTRL_PERM_EDIT_POST')")
public String editPermission(@Valid @ModelAttribute PermissionDTO permissionDTO,
BindingResult result, RedirectAttributes redirectAttrs,
@RequestParam(value = "action", required = true) String action) {
logger.debug("IN: Permission/edit-POST: " + action);
if (action.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
String message = messageSource.getMessage("ctrl.message.success.cancel",
new Object[] {"Edit", businessObject, permissionDTO.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("message", message);
} else if (result.hasErrors()) {
logger.debug("Permission-edit error: " + result.toString());
redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.permissionDTO", result);
redirectAttrs.addFlashAttribute("permissionDTO", permissionDTO);
return "redirect:/permission/edit?id=" + permissionDTO.getId();
} else if (action.equals(messageSource.getMessage("button.action.save", null, Locale.US))) {
logger.debug("Permission/edit-POST: " + permissionDTO.toString());
try {
Permission permission = getPermission(permissionDTO);
permissionService.updatePermission(permission);
String message = messageSource.getMessage("ctrl.message.success.update",
new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("message", message);
} catch (DuplicatePermissionException unf) {
String message = messageSource.getMessage("ctrl.message.error.duplicate",
new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("error", message);
return "redirect:/permission/list";
} catch (PermissionNotFoundException unf) {
String message = messageSource.getMessage("ctrl.message.error.notfound",
new Object[] {businessObject, permissionDTO.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("error", message);
return "redirect:/permission/list";
} catch (RoleNotFoundException unf) {
String message = messageSource.getMessage("ctrl.message.error.notfound",
new Object[] {"role ids", permissionDTO.getPermRoles().toString()}, Locale.US);
redirectAttrs.addFlashAttribute("error", message);
return "redirect:/permission/list";
}
}
return "redirect:/permission/list";
}
@RequestMapping(value = "/edit", method = RequestMethod.GET)
@PreAuthorize("hasRole('CTRL_PERM_EDIT_GET')")
public String editPermissionPage(@RequestParam(value = "id", required = true)
Integer id, Model model, RedirectAttributes redirectAttrs) {
logger.debug("IN: Permission/edit-GET: ID to query = " + id);
try {
if (!model.containsAttribute("permissionDTO")) {
logger.debug("Adding permissionDTO object to model");
Permission perm = permissionService.getPermission(id);
PermissionDTO permissionDTO = getPermissionDTO(perm);
logger.debug("Permission/edit-GET: " + permissionDTO.toString());
model.addAttribute("permissionDTO", permissionDTO);
}
return "permission-edit";
} catch (PermissionNotFoundException e) {
String message = messageSource.getMessage("ctrl.message.error.notfound",
new Object[] {"user id", id}, Locale.US);
model.addAttribute("error", message);
return "redirect:/permission/list";
}
}
@RequestMapping(value = "/delete", method = RequestMethod.GET)
@PreAuthorize("hasRole('CTRL_PERM_DELETE_GET')")
public String deletePermission(
@RequestParam(value = "id", required = true) Integer id,
@RequestParam(value = "phase", required = true) String phase,
Model model, RedirectAttributes redirectAttrs) {
Permission permission;
try {
permission = permissionService.getPermission(id);
} catch (PermissionNotFoundException e) {
String message = messageSource.getMessage("ctrl.message.error.notfound",
new Object[] {"permission id", id}, Locale.US);
redirectAttrs.addFlashAttribute("error", message);
return "redirect:/permission/list";
}
logger.debug("IN: Permission/delete-GET | id = " + id + " | phase = " + phase + " | " + permission.toString());
if (phase.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
String message = messageSource.getMessage("ctrl.message.success.cancel",
new Object[] {"Delete", businessObject, permission.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("message", message);
return "redirect:/permission/list";
} else if (phase.equals(messageSource.getMessage("button.action.stage", null, Locale.US))) {
logger.debug(" deleting permission : " + permission.toString());
model.addAttribute("permission", permission);
return "permission-delete";
} else if (phase.equals(messageSource.getMessage("button.action.delete", null, Locale.US))) {
try {
permissionService.deletePermission(permission.getId());
String message = messageSource.getMessage("ctrl.message.success.delete",
new Object[] {businessObject, permission.getPermissionname()}, Locale.US);
redirectAttrs.addFlashAttribute("message", message);
return "redirect:/permission/list";
} catch (PermissionNotFoundException e) {
String message = messageSource.getMessage("ctrl.message.error.notfound",
new Object[] {"permission id", id}, Locale.US);
redirectAttrs.addFlashAttribute("error", message);
return "redirect:/permission/list";
}
}
return "redirect:/permission/list";
}
@PreAuthorize("hasAnyRole('CTRL_PERM_EDIT_GET','CTRL_PERM_DELETE_GET')")
public PermissionDTO getPermissionDTO(Permission perm) {
List<Integer> roleIdList = new ArrayList<Integer>();
PermissionDTO permDTO = new PermissionDTO();
permDTO.setId(perm.getId());
permDTO.setPermissionname(perm.getPermissionname());
for (Role role : perm.getPermRoles()) {
roleIdList.add(role.getId());
}
permDTO.setPermRoles(roleIdList);
return permDTO;
}
@PreAuthorize("hasAnyRole('CTRL_PERM_ADD_POST','CTRL_PERM_EDIT_POST')")
public Permission getPermission(PermissionDTO permissionDTO) throws RoleNotFoundException {
Set<Role> roleList = new HashSet<Role>();
Permission perm = new Permission();
Role role = new Role();
perm.setId(permissionDTO.getId());
perm.setPermissionname(permissionDTO.getPermissionname());
if (permissionDTO.getPermRoles() != null) {
for (Integer roleId : permissionDTO.getPermRoles()) {
role = roleService.getRole(roleId);
logger.debug(" ROLE: " + role.toString());
roleList.add(role);
}
perm.setPermRoles(roleList);
}
logger.debug(" PERM: " + perm.toString());
return perm;
}
}
Similar to the User controller, we are autowiring two services: one for the PermissionService and one for the RoleService. Permission maintenance will provide the capability to add/update/delete Roles associated with a permission.
The list, add, edit, and delete methods look nearly identical to the same methods on the User controller, and were discussed in the User CRUD posts. Note that the Permission add and edit pages have to handle an exception thrown if a role is not found...not very likely, but it needs to be handled.
At the bottom of the conroller code are methods to convert DTOs to Entities, and Entities to DTOs. The functionality to notice is the handling of the Permission objects associations with Role objects.
In part 2, we will create the three Thymeleaf pages and show some screenshots of the Permissions CRUD pages.