0

I am leveraging Spring to create a web application where a PortalUser can access and create a Ticket accessible only by their Company.

By default, any authenticated user with read/write access can perform all CRUD operations. The problem is, an authenticated user can perform an operation outside of their Company by choosing an id they should not have access to.

Company Foo could very easily delete, view, etc a ticket from Bar, buy directly accessing the Ticket at example.com/tickets/{any id}

@Controller
@RequestMapping("/tickets")    
public class TicketController {
    @RequestMapping(value = "/{id}", produces = "text/html")
    public String TicketController.show(@PathVariable("id") Long id, Model uiModel) {
           ...
    }
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = "text/html")
    public String TicketController.delete(@PathVariable("id") Long id, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "size", required = false) Integer size, Model uiModel){
    ...
    }
    @RequestMapping(method = RequestMethod.GET, produces = "text/html")
    public String TicketController.listDatatables(Model uiModel, HttpServletRequest request) {
    ...
    }
}

Without modifying each method in every controller, is there a way to ensure that users cannot access data that is outside of the intended scope?

It seems that a custom Filter, could be of use, and I am able to filter out Companies on GET's by cloning and modifying the parameter map, but I haven't found a way solution would work for all methods.

Community
  • 1
  • 1
Jaym
  • 301
  • 5
  • 14

1 Answers1

0

It seems that a way to prevent users from creating or modifying tickets is to add a validator to Ticket.

@RooJavaBean
@RooToString
@RooJpaActiveRecord(sequenceName = "TICKET_SEQ", finders = { "findTicketsByCompany", "findTicketsByPerson" })
public class Ticket {
    @ValidCompany
    Company;
    //....
}

We can add the logic to grab the current user's credentials, and check that they are in the same Company of the Ticket they are creating / modifying.

public class CompanyValidator implements ConstraintValidator<ValidCompany, Company> {

@Override
public void initialize(ValidCompany validCompany) {
}

public CompanyValidator() {
}

@Override
public boolean isValid(Company company, ConstraintValidatorContext constraintValidatorContext) {
    //logic 
    Company portalUserCompany = null;
    try {
        PortalUser portalUser = (PortalUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        portalUserCompany = portalUser.getCompany(); 
        //In this case we are getting the PortalUser's Company, but you could get a user's authorities instead.
    } catch (NullPointerException e) {
        e.printStackTrace();
    }
    return portalUserCompany != null && portalUserCompany.getId().equals(company.getId());

}

And the Interface:

@Documented
@Constraint(validatedBy = CompanyValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidCompany {
    String message() default "{validator.company}";//Error message to be displayed if the CompanyValidator isValid returns false
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
}
Jaym
  • 301
  • 5
  • 14