Security filtering entity collection with jax-rs and jersey - filter

I've been looking around on how to filter results based on certain security roles. I've been looking at filters but this just seems to filter out certain fields rather than the complete entity itself. Let me try to explain what I need. Lets say I have 2 entity classes:
public class Project {
private Long id;
private String name;
private String description;
// getters and setters
}
public class User {
private Long id;
private String name;
private String email;
// getters and setters
}
resource:
#Path("projects")
#Produces("application/json")
public class ProjectsResource {
#GET
public List<Project> getProjects() {
return getDetailedProjects();
}
}
Also I have 2 companies, each with their own projects. For convenience lets call them company A and company B.
What I would like to achieve is that 3 users all using this same resource get different results based on their security role.
User 1: the Super user, should get everything
User 2: manager for company A, should only get projects for company A
User 3: manager for company B, should only get projects for company B
How should I go about doing this? It just doesn't seem right to do this filtering in each resource.
I'm using jax-rs, with role based security. Users are linked to a role which grants them access to projects for a specific company.

I think you would have to parameterize your getDetailedProjectsmethod with the accessing user.
To achieve that you can inject the jersey SecurityContextinto your Resource like so (from jersey documentation):
#Path("basket")
public ShoppingBasketResource get(#Context SecurityContext sc) {
if (sc.isUserInRole("PreferredCustomer") {
return new PreferredCustomerShoppingBasketResource();
} else {
return new ShoppingBasketResource();
}
}
The securityContext.getUserPrincipal().getName()method will allow you to get hold of the requesting user and map that to a method parameter (or use it plain).

Related

Spring boot REST API best way to choose in client side which field to load

Hi I have implemented a mock solution to my problem and I'm pretty sure something better already exist.
Here's that I want to achieve :
I have created a point to load categories with or without subCategories
/api/categories/1?fields=subCategories
returns
{
"id":"1",
"name":"test",
"subCategories":[{
"id":"1",
"name":"test123"
}]
}
/api/categories/1
returns
{
"id":"1",
"name":"test"
}
My entities
#Entity
class Category{
#Id
private String id;
private String name;
private Set<SubCategory> subCategories;
}
#Entity
class SubCategory{
#Id
private String id;
private String name;
}
I have removed services since this is not the point.
I've created CategoryDTO and SubCategoryDTO classes with the same fields as Category and SubCategory
The converter
class CategoryDTOConverter{
CategoryDTO convert(Category category,String fields){
CategoryDTO dto=new CategoryDTO();
dto.setName(category.getName());
if(StringUtils.isNotBlank(fields) && fields.contains("subCategories"){
category.getSubCategories().forEach(s->{
dto.getSubcategories().add(SubCategoryDTOConverter.convert(s));
}
}
}
}
I used com.cosium.spring.data.jpa.entity.graph.repository to create an EntityGraph from a list of attribute path
#Repository
interface CategoryRepository extends EntityGraphJpaRepository<Category, String>{
Optional<T> findById(String id,EntityGraph entityGraph);
}
Controller
#RestController
#CrossOrigin
#RequestMapping("/categories")
public class CategoryController {
#GetMapping(value = "/{id}")
public ResponseEntity<CategoryDTO> get(#PathVariable("id") String id, #RequestParam(value="fields",required=false) String fields ) throws Exception {
Optional<Category> categOpt=repository.findById(id,fields!=null?EntityGraphUtils.fromAttributePaths(fields):null);
if(categOpt.isEmpty())
throws new NotFoundException();
return ResponseEntity.ok(categoryDTOConverter.convert(categOpt.get(),fields);
}
}
This is a simple example to illustrate what I need to do
I don't want to load fields that clients doesn't want to use
How could I do this in a better way ?
Take a look at GraphQL since it is a perfect match for your use case. With GraphQL it is the client that decides which attributes it wants to receive by providing in the POST request body exactly which attributes are needed to be included in the response. This is way more manageable than trying to handle all this on your own.
Spring Boot recently added its own Spring GraphQL library, so it is quite simple to integrate it in your Spring Boot app.

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 handle user privilege in spring security and inside thyemeleaf

I'm not new to java (i did some projects with javafx and jee ) but I'm new to the whole spring thing
Here is what my scenario is
All users have same level of access but some have more accee defiened by their role in the database ( there can be upto 25!(yes that is factorial) roles all created dynamically by the main admin) .
My template is made of 3 fragments (header with admin links that are available to users associated to their roles , side bar available to everyone and footer)
So I'm stuck in how to make a global variable for the header part to check the logged in user on each page load and choose the correct links
I know how to do this with jee and jsp but not with spring
Thanks in advance.
Use a repository and hold your session data after login like below
#Repository
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class LoginRepository {
private String accessToken;
private String name;
private String password;
private String userId;
private String rolename;
private String department;
}
then use #ControllerAdvice to pass this in every Model
#Autowired
LoginRepository settings;
#ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("rolenamecode", settings.getRolename());
.......
}
Then based on your role or other parameters (th:if) you can load your links in you page

Have one Rest repository json with everything and one with fields excluded

I have two entities: Book and Category and a repository for both. In the controller, I have set up the methods correctly as such:
#RequestMapping(value="/books", method = RequestMethod.GET)
#CrossOrigin
public #ResponseBody List<Book> bookListRest() {
return (List<Book>) bookRepository.findAll();
}
This obviously shows all books and every field in the entity that isn't #JsonIgnore'd. The problem is, I need to have:
One page with Book data (book name, author name, isbn..) without category
One page with Category data (Category name) without books
One page with Everything (book data along with categories where they belong in)
How can one accomplish this?
I somehow need to in a way ignore #jsonignore on some occasions. Should I make a new entity that extends say, Question and also make a repository for that? Surely that can't be the correct way to do this.
As khalid Ahmed Said you can use costum dtos or you can add Filters to ignore specific fields in Jackson. First, we need to define the filter on the java object:
#JsonFilter("myFilterBook")
public class Book{
...
}
#JsonFilter("myFilterCategory")
public class Category{
...
}
Before you return your ResponseBody you try to use ObjectMapper (Jackson):
The case of one page with Book data (book name, author name, isbn..) without category:
ObjectMapper mapper = new ObjectMapper();
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter
.serializeAllExcept("category");
FilterProvider filters = new SimpleFilterProvider()
.addFilter("myFilterBook", theFilter);
String dtoAsString = mapper.writer(filters).writeValueAsString(book);
You can do the same think by putting what you want o ignore for the other example.
And for more details to ignore field during marshalling with jackson you can check here
What about using DTOs data transfer objects
you can create multiple DTOs to use them in the response of your API
DTO is a pojo class that customize the returning data from your entity
public class BookWithoutCategoryDTO {
private String name;
private String authorName;
.....
/// and make setters and getters for them
}
public class BookWithCategoryDTO {
private String name;
private String authorName;
private String category;
.....
/// and make setters and getters for them
}
and create your custom mapper to convert from Book to BookDTO

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