I have an application that have two domain model
Organization and TicketQuestion .
Authenticated User want to create ticket that have an organization property to solve that
each user permit to some organization like this:
User1 permit to Organization1
User2 permit to Organization2
TicketController.java have save method that create ticket.
I have this vulnerability: User1 can invoke method with ticket that have Organization2( that dose not have permission to it ).
I am using Hibernate filter for authorize data in GET methods but i dont know how can i protect data that user want persist and dose not have permission ??;
/ticket/save
{
id:-1,
organization:{
id:2,
title:'organization2' //not allowed this organization
}
}
#Entity
#Table(name = "core_organization_structure")
public class OrganizationStructure {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "title", nullable = false)
private String title;
}
#Entity
#Table(name = "core_Ticket")
public class Ticket {
..some prop
#ManyToOne
#JoinColumn(name = "org_id", nullable = false)
private OrganizationStructure org;
}
When the form is submitted, you need to load the authenticated user's permissions and check that they are authorized to perform the action they are attempting to perform.
If the user is attempting to create a ticket for an organization that they do not have permissions to; don't persist the record, and handle it appropriately. (Throw an exception, return a 401, etc...)
Related
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();
}
I have a entity relationship setup as #OneToOne. For example (User <--> Pet)
#Entity
public class Pet {
#OneToOne
#JoinColumn(name = "user_id", nullable = true)
User user;
}
#Entity
public class User {
#OneToOne(mappedBy = "user")
Pet pet;
}
To my surprise, Spring will allow you to assign multiple entities into the relationship (Multiple Pets associated with User) when you save via the repositories.
//for example:
pet.user = user1;
pet2.user = user1;
petRepository.save(pet);
petRepository.save(pet2); //allowed
But then after the save, the database will now be corrupted.
By calling petRepository.findAll or findById or anything really...will now yield this error
nested exception is org.hibernate.HibernateException: More than one row with the given identifier was found: 1245, for class: package.models.Pet
So what is the proper way to ensure OneToOne relationship so as to not corrupt the db?
I am working with Spring cloud (microservices) and I have implemented security with JWT token.
in my security application, I have entities like User, Role and UserRole.
so Every request first comes to the ZOOL service and it calls Authentication service and Authentication service creates/returns JWT token.
Also, I have another microservice-rest application (Questions-app) that needs JWT token.
in the Questions-app I have a Question entity that contains authorId field.
#Entity
#Table(name="QUESTION")
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", updatable = false, nullable = false)
private long id;
#Column(name = "AUTHOR")
private long authorId;
#Column(name = "TITLE")
private String title;
}
Now, it is not clear for me, is it right to set authorId long type or I should create User, Role, UserRole entities (just simple copy from AUTH project) in the questions-app and set "AUTHOR" column like that
#OneToOne
#JoinColumn(name="AUTHOR")
private User user;
I know that in the first option when I need show question and user's name on the webpage, then I should call 2 services (one from question-app (fetch question) and another from auth service (fetch user information by author id)
I would like to know what is the best practises?
If you have common database for all of these microservices and you need User related information based on question id.
Then instead of doing another database call for user, you can directly do #OneToOne to User.
From your question it is better to go for 2 option.
My User class looks like this :
#Data
#Entity
public class User {
#Id
Long userID;
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = new ArrayList<>();
}
And my ClassRoom class like this :
#Data
#Entity
public class ClassRoom {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long classRoomID;
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList<>();
}
And in my UserController class, I have :
#PostMapping("user/{id}/c")
User addClassRoom(#PathVariable Long id,#RequestBody ClassRoom newClassRoom)
{
logger.debug(repository.findById(id));
return repository.findById(id)
.map(user -> {
user.getClassRooms().add(newClassRoom);
user.setClassRooms(user.getClassRooms());
return repository.save(user);
})
.orElseGet(() -> {
return null;
});
}
And I POST and empty JSON ({}) and I see no change in my users. The Classroom or an empty Classroom doesn't get added in the User.
What is the problem here? How can I resolve this ?
user.getClassRooms().add(newClassRoom); is suffice, user.setClassRooms(user.getClassRooms()); not required.
You will have to perform cascade save operation.List all cascade types explicitly and don't use mappedBy, instead use joincolumns annotation.
Can you paste the logs, please? Is Hibernate doing any insert into your table? Has the database schema been created in the DB correctly? One thing I recommend you to do is to add a custom table name on the top of your User class, using annotations like so: #Table(name = "users"). In most SQL dialects user is a reserved keyword, hence it is recommended to always annotate User class a bit differently, so that Hibernate won't have any problems to create a table for that entity.
IMO you must find classRoom by its id from repository, if it's new, you must create a new entity and save it first. Then assign it to user and save it.
The object you receive from the post method was not created by the entity manager.
After using user.getClassRooms().add(newClassRoom);
We must use userRepository.save(user);
I wish to know how to delete a many-to-many association via a REST call. I am able to create records, and associated them, but do not understand how to delete.
I have a Spring Boot project where i'm using REST and HATEOAS to by pass Services and Controllers and expose my Repository directly.
I have a User Model/Domain class
#Entity
#Table(name = "usr")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Version
private long version = 0;
#Id
#GeneratedValue(generator="optimized-sequence")
private Long id;
#Column(nullable = false, unique = true, length = 500)
#Size(max = 500)
private String userName;
#Column(nullable = false, length = 500)
#Size(max = 500)
private String firstName;
#Column(nullable = false, length = 500)
#Size(max = 500)
private String lastName;
#ManyToMany( fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable( name="user_role",
joinColumns={ #JoinColumn( name = "user_id",
nullable = false
)
},
inverseJoinColumns={ #JoinColumn( name="role_id",
nullable=false
)
}
)
private Set<Role> roles = new HashSet<Role>(0);
...Getters/Setters Below...
As as you can see, I have a roles member that is Many-To-Many association with Role class, of which the code is such:
#Entity
public class Role {
#Id
#GeneratedValue(generator="optimized-sequence")
private Long id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String description;
...Getters/Setters Below...
My repositories look like so:
UserRepository
public interface UserRepository extends
JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
List<User> findByUserName(String username);
}
RoleRepository
public interface RoleRepository
extends JpaRepository<Role, Long> {
}
Now, all is well. When I access the project root from a browser, I get the repository index/directory in JSON+HAL format. Wonderful.
(Note I'm remove the http:// part from the test below because StackOverflow is counting it towards my links quota)
I, using WizTools REST Client, HTTP.POST to the Role ( localhost:8080/resttest/roles ) repository and create a new Role. Success, Role ID #4 created.
Then I POST to the User repository to create a User ( localhost:8080/resttest/users ). Success, User ID #7 created.
Then I PUT to the User repository to create an association with the role:
PUT localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body: localhost:8080/resttest/roles/4
Great! Association made. User 9 is now associated with Role 4.
Now I can't for the life of me figure out how to DELETE this association.
I'm sending an HTTP DELETE instead of PUT with the same command as above.
DELETE localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body: localhost:8080/resttest/roles/4
I get back: HTTP/1.1 405 Method Not Allowed
{
"timestamp":1424827169981,
"status":405,
"error":"Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message":"Request method 'POST' not supported"
}
Although creating a PUT request with remaining elements does the trick, DELETE is an accepted command to delete an association resource and is in most cases easier to use.
As for your example, this should work:
DELETE localhost:8080/resttest/users/7/roles/4
On a separate note, when creating the association, it is expected to have URIs in the payload. You shouldn't need to write the whole URL in the body, this should be enough:
PUT localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body: /roles/4
Hope this helps.
From the docs:
DELETE
...
405 Method Not Allowed - if the association is non-optional
PUT means that you replace the whole roles Set. So to remove a single link you PUT all remaining links. If you only have a single link and want to remove it you would PUT an empty collection:
PUT localhost:8080/resttest/users/7/roles
Content-type: uri-list
Body:
BTW: You won't send a body with a DELETE request. It doesn't make sense.
EDIT
See also this answer from the developer of Spring HATEOAS.