I am using Dropwizard with JDBI. I have a typical dao for user data:
public interface UserDao
{
#SqlQuery("select * from users where role = :id")
#Mapper(UserMapper.class)
String findNameById(#BindBean Role role);
}
The user itself has an attribute with a Role type:
class User
{
private Role role;
/* the rest: other attributes, getters, setters, etc. */
}
Role is contained in another table called roles. Now, I need to map Role in the mapper, but I do not want to change the SELECT ... statement to add the JOIN roles ... part. We all know how joins affect queries and in the long run I'd like to avoid any joins if possible.
I know, that ResultSetMapper interface has a map() method, which gets a StatementContext passed to it. That context has a getBinding() method, which returns a Binding class with all the data I need:
named = {HashMap$Node#1230} size = 3
0 = {HashMap$Node#1234} "id" -> "1"
1 = {HashMap$Node#1235} "name" -> "TestRole"
2 = {HashMap$Node#1236} "class" -> "class com.example.Role"
But that class com.example.Role is not an instance of Role, it's an instance of Argument and I can't work with it.
So, is there a way to get that Role argument and I just don't see it or do I have to instantiate it (again...) from the binding arguments (obviously they are there as debugger shows)?
I finally solved it by using a custom binder. First, I modified UserDao to use #BindRole instead of #BindBean.
Next, I had to create the binder for the role. Here the role is bound manually on separate values:
#BindingAnnotation(BindRole.RoleBinderFactory.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface BindRole
{
public static class RoleBinderFactory implements BinderFactory
{
public Binder build(Annotation annotation)
{
return new Binder<BindRole, User>()
{
public void bind(SQLStatement q, BindRole bind, Role role)
{
q.bind("id", role.getId());
q.bind("name", role.getName());
q.define("role", role);
}
};
}
}
}
Notice the define() method, it is responsible for setting attributes in StatementContext, so don't overlook it.
Next, in the mapper I just have to get the Role with getArgument():
Role role = new Role();
role.setId(1);
role.setName("TestRole");
Role r = (Role) statementContext.getAttribute("role");
boolean equals = e.equals(role);
In the debugger equals is shown as true, so problem solved. Woohoo.
Related
My Java Spring application, which uses ACLs, has a service method to retrieve all the objects corresponding to a given user:
#Override
#PostFilter("hasPermission(filterObject, 'READ') or hasPermission(filterObject, 'ADMINISTRATION')")
public List<SomeClass> findAll() {
return SomeClassRepository.findAll();
}
Unfortunately, where there are many objects in the database, this method takes too much time to complete (over 1 second). Probably because it will first fetch all the objects from the database, and then filter them one by one in memory. How can I optimize this without losing the benefits of Spring ACLs?
Edit: the solution I came up with for now is to create repositories for the acl_sid and acl_entry repositories and to fetch the IDs of the object of interest through those repositories. This gives me a 10x improvement in execution time compared to the method above. The new code looks like this:
#Override
#PostFilter("hasPermission(filterObject, 'READ') or hasPermission(filterObject, 'ADMINISTRATION')")
public List<SomeClass> findAll() {
List<SomeClass> result = new ArrayList<SomeClass>();
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
AclSid sid = aclSidRepository.findBySid(Long.toString(userId));
List<AclEntry> aclEntries = aclEntryRepository.findBySid(sid);
for (AclEntry aclEntry : aclEntries) {
AclObjectIdentity aclObjectIdentity = aclEntry.getAclObjectIdentity();
AclClass aclClass = aclObjectIdentity.getObjectIdClass();
if (aclClass.getClassName().equals("com.company.app.entity.SomeClass")) {
Optional<SomeClass> SomeClass = SomeClassRepository
.findById(aclObjectIdentity.getObjectIdIdentity());
if (SomeClass.isPresent()) {
result.add(SomeClass.get());
}
}
}
return result;
}
As Spring filters the information in memory the performance will depend on the actual number of results: if there is a large amount of them, I am afraid that perhaps only caching your repository results before filtering the information may be a suitable solution.
To deal with the problem, you can filter the results at the database level. Two approaches come to my mind:
Either use Specifications, and filter the results at the database level taking into account the information about the principal exposed by Spring Security SecurityContext and including the necessary filter Predicates in order to restrict the information returned.
Or, if you are using Hibernate, use entity filters to again, based on the information about the principal exposed by Spring Security, apply the necessary data restrictions. Please, see this related SO question which provides great detail about the solution.
Please, consider for instance the use case of the Spring Data Specifications.
Instead of SomeClass, let's suppose that we are working with bank accounts. Let's create the corresponding entity:
#Entity
public class BankAccount {
#Id
private String accountNumber;
private Float balance;
private String owner;
private String accountingDepartment;
//...
}
And the corresponding repository:
public interface BankAccountRepository extends Repository<BankAccount, String>, JpaSpecificationExecutor<BankAccount, String> {
}
In order to filter the information depending on the user who is performing the query, we can define an utility method that, based on the user permissions, returns a List of Predicates that we can add later to the ones we are using in a certain Specification when filtering the bank accounts:
public static List<Predicate> getPredicatesForRestrictingDataByUser(Root<BankAccount> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// I realized in your edit that you are returning the user id instead of the user object.
// There is nothing wrong with it but you are losing a valuable information: if you provide
// a convenient UserDetails implementation you can have direct access to the authorities a user has, etc
User user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// Restrict data based on actual permissions
// If the user is an admin, we assume that he/she can see everything, and we will no return any predicates
if (hasAuthority(user, 'ADMINISTRATION')) {
return Collections.emptyList();
}
// Let's introduce the accounting manager role.
// Suppose that an accounting manager can see all the accounts in his/her department
if (hasAuthority(user, 'ACCOUNTING_MANAGER')) {
return Collections.singletonList(cb.equal(root.get(BankAccount_.accountingDeparment), user.getDepartment()))
}
// In any other case, a user can only see the bank account if he/she is the account owner
return Collections.singletonList(cb.equal(root.get(BankAccount_.owner), user.getId()));
}
Where hasAuthority can look like:
public static boolean hasAuthority(User user, String... authorities) {
if (user instanceof UserDetails) {
for (String authority : authorities) {
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.findAny(a -> a.equals(authority))
.isPresent();
}
}
return false;
}
Now, use these methods when constructing your Specifications. Consider for instance:
public static Specification<BankAccount> getBankAccounts(final BankAccountFilter filterCriteria) {
return new Specification<BankAccount>() {
#Override
public Predicate toPredicate(Root<BankAccount> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
// Build your predicate list according to the user provided filter criteria
String accountNumber = filterCriteria.getAccountNumber();
if (accountNumber != null) {
predicates.add(cb.equal(root.get(BankAccount_.accountNmber), accountNumber);
}
//...
// And now, restrict the information a user can see
// Ideally, define getPredicatesForRestrictingDataByUser in a generic class more suitable for being reused
List<Predicate> predicatesForRestrictingDataByUser = getPredicatesForRestrictingDataByUser(root, query, cb);
predicates.addAll(predicatesForRestrictingDataByUser);
Predicate predicate = cb.and(predicates.toArray(new Predicate[predicates.size()]));
return predicate;
}
};
}
Please, forgive me for the simple use case, but I hope you get the idea.
The solution proposed in his comment by #OsamaAbdulRehman looks interesting as well, although I honestly never tested it.
I have User class like this :
#Data
#Entity
public class User {
#Id #GeneratedValue Long userID;
String eMail;
String passwordHash;
}
And I have data like this :
[{"userID":1,"passwordHash":"asdasd","email":"admin#admin.com"},
{"userID":2,"passwordHash":"12345","email":"admin1asdasd#admin.com"}]
I have two method in my controller class, one - to get single user :
#GetMapping("/user/{id}")
User one(#PathVariable Long id) {
return repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
Other method to retrieve all user :
#GetMapping("/user")
List<User> all() {
return repository.findAll();
}
In my browser, going to this address - http://localhost:8080/user , I can see these data. And if I goto http://localhost:8080/user/id I can get a specific value.
Now my question is how can access data like http://localhost:8080/user/email/passwordHash? I am quite sure that it is not possible, because I haven't stored data in that way.
As my main target is to verify login, I have already written a #Query in my repository interface. Here it is :
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select u from User u where u.eMail = ?1 and u.passwordHash = ?2")
List<User> listByLoginCredential(String emailAddress,String passwordHash);
}
Can Anyone tell me how can I do this,use this method of this interface?
I think you can can achieve what you want by adding the following method to the controller class:
#GetMapping("/user/{emailAddress}/{passwordHash}")
List<User> byMailAndPassword(#PathVariable String emailAddress, #PathVariable String passwordHash) {
return repository.listByLoginCredential(emailAddress, passwordHash)
}
On the other hand you say that your main goal is to verify login, so it looks like you are doing authentication. If you have time you should look into doing it with spring security https://spring.io/guides/gs/securing-web/#initial
Maybe this help https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.
Or you can also create procedure in Database and call stored procedure with Spring boot.
Login is related to security so ideally you should create a separate post method and use the repository method. Always make sure to follow the best practice.
Spring security is something you can utilize for better authentication and authorization.
My group is planning to use Apollo Gateway for federation. Therefore, we need to produce our schemas a little bit differently.
Can we produce something like this with using your amazing lib?
extend type User #key(fields: "id") {
id: ID! #external
reviews: [Review]
}
You want to add some fields and directives to a type?
You can use #GraphQLContext to attach external methods as fields. Or even provide a custom ResolverBuilder that returns additional Resolvers (these later get mapped to fields).
To add directives, you can create annotations meta-annotated with #GraphQLDirective (see the tests for examples).
Lastly, you can of course provide a custom TypeMapper for User and fully take control of how that type gets mapped.
E.g. you can make an annotation like:
#GraphQLDirective(locations = OBJECT)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Key {
public String[] fields;
}
If you then place this annotation on a type:
#Key(fields = "id")
public class User {
#External //another custom annotation
public #GraphQLId #GraphQLNonNull String getId() {...}
}
it will get mapped as:
type User #key(fields: "id") {
id: ID! #external
}
I presume you know about #GraphQLContext, but in short:
//Some service class registered with GraphQLSchemaBuilder
#GraphQLApi
public class UserService {
#GraphQLQuery
public List<Review> getReviews(#GraphQLContext User user) {
return ...; //somehow get the review for this user
}
}
Because of #GraphQLContext, the type User now has a review: [Review] field (even though the User class does not have that field).
I'm rewriting an application, this time using a RESTful interface from Spring. I'm presuming that server-side authorization is best. That is:
Supppose user 1 works this REST repository. He/she accesses mysite.com/heroes/1 and gets the (id = 1) hero from the hero table.
User 2 doesn't have rights to see the (id = 1) hero, but could craft a cURL statement to try anyway. I claim the server should prevent user 2 from accessing the (id = 1) hero.
I believe that the server can extract a JWT payload that gives me the user name or password (I put it in there). From that payload the server fetches the user's account and knows what heroes he/she is entitled to see.
I have already accomplished this goal through services and DAO classes. However, the Spring Boot and JPA tutorials I see promote using CrudRepository implementations to reduce coding. I'd like to know how to do my filtering using this technology.
Here is an example from the web:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
}
When mysite.com/heroes/1 is accessed it automagically returns the data from hero (id = 1). I'd like to instruct it to let me choose which ID values to permit. That is, at runtime a query parameter is provided to it through code.
As a test I provided this code:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
#Query ("from Hero h where id in (1, 3, 5)")
public Hero get();
}
However, it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
How do I get to my desired goal?
Thanks, Jerome.
UPDATE 5/13, 5:50 PM
My request is being misunderstood, so I further explain my intent.
Users 1 and 2 are ordinary users, accessing their accounts.
Each user must be confined to his/her own account.
A user can't cheat by crafting requests for other peoples' data.
Thus the need for the server to extract a user ID, or such, from a JWT token and apply it in code to whatever causes the /heroes query to work.
My original example originated with this tutorial. In it the only Java classes are Hero and HeroRepository. There are no explicit classes for DAO, services or controllers. The included Spring libraries let all of the /heroes fetching occur without further coding.
Thanks again for all of your interest and help. Jerome.
You can create a custom #Query, that uses informations (here: id) of the logged in user. With this solution an user have only access to an entity with the same id as he has.
#Override
#Query("SELECT h FROM Hero h WHERE h.id=?1 AND h.id=?#{principal.id}")
public Hero findOne(Long id);
You need to enable SpEl for #Query (link) and create an custom UserDetailsService (link) with custom UserDetails, that contains the id of the user, so you can do principal.id.
In the same way you should secure the findAll() method.
I have created HeroRepository to resolve all the queries up to my understanding.
I'd like to instruct it to let me choose which ID values to permit.
You can achieve the same using.
List<Hero> findByIdIn(List<Long> ids);
Or, if you prefer Query
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
I cannot see your Controller/Service methods, so I am assuming that findOne() is being called. You can prevent it using..
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
OR, if you want more control over your method invocations, you can also use #PreAuthorize from spring-security.
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
Summary
public interface HeroRepository extends CrudRepository<Hero, Long> {
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
// If u want to pass ids as a list
List<Hero> findByIdIn(List<Long> ids);
// Alternative to above one
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
}
PS: Note that I am returning Optional<Hero> from the method. Optional.empty() will be returned if query produces no results. This will force us to check if the value is present before doing any operation, thereby avoiding NullPointerException.
use this code for Controller : -
#RestController
#RequestMapping("/cities")
public class CityController {
private static final Logger logger = LoggerFactory.getLogger(CityController.class);
#Autowired
private CityService cityService;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public RestResponse find(#PathVariable("id") Long id) {
.
.
}
use below code for Repo :-
public interface CityRepo extends JpaRepository<FCity, Long> {
#Query("select e from FCity e where e.cityId = :id")
FCity findOne(#Param("id") Long id);
}
use below code for service :-
#Service
#Transactional
public class CityService {
#Autowired(required = true)
private CityRepo cityRepo;
public FCity findOne(Long id) {
return cityRepo.findOne(id);
}
}
I have a following domain model:
Playlist -> List<PlaylistItem> -> Video
#Entity
class Playlist{
// id, name, etc
List<PlaylistItem> playlistItems;
// getters and setters
}
#Entity
class PlaylistItem{
// id, name, etc.
Video video;
// getters and setters
}
#Entity
class Video{
// id, name, etc.
boolean isDeleted;
// getters and setters
}
And my repository:
public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
List<Playlist> findAll();
}
Now, how do I return a playlist with only existing videos, ie, if there are three videos in the database assigned to that playlist item and one of those videos has isDeleted set to true, then I need to get only two items instead.
All you have to do is declare this method on your PlaylistRepository interface:
List<Playlist> findByPlaylistItemsVideoIsDeleted(boolean isDeleted);
And call it like this:
playListRepository.findByPlaylistItemsVideoIsDeleted(false);
That will return all playlist with videos that are not removed.
You may have already resolved this issue, but I thought I would contribute this in hopes it might help you, or anyone else visiting this page.
Using Spring JPA Specifications, you would:
Enable your PlaylistRepository to use JPA Specifications
Write the Specification as a reusable method
Make use of the Specification as the query
Here are the details.
1. Implement JpaSpecificationExecutor
Update PlaylistRepository to implement JpaSpecificationExecutor. This adds find* methods that accept Specification<T> parameters to your PlaylistRepository.
public interface PlaylistRepository extends JpaRepository<Playlist, Long>,
JpaSpecificationExecutor<Playlist> {
}
2. Create the Specification
Create a class with a static method for use in creating a reusable Specification.
public final class PlaylistSpecifications {
private PlaylistSpecifications() {}
public static Specification<Playlist> hasExistingVideos() {
return (root, query, cb) -> {
return cb.equal(root.join("playlistItems").join("video")
.get("isDeleted"), false);
};
}
}
Using root.join (and subsequent joins) is similar to using JOIN in SQL. Here, we are joining on the fields of classes, instead of on columns of tables.
3. Issue the Query
I don't know how you plan to issue your query, but below is an example of how it could be done in a "service" class:
#Service
public class PlaylistService {
#Autowired
private PlaylistRepository playlistRepository;
public List<Playlist> findPlaylistsWithExistingVideos() {
Specification<Playlist> spec = PlaylistSpecifications.hasExistingVideos();
return playlistRepository.findAll(spec);
}
}
Hope this helps!
Maksim, you could use the #query annotation like this :
public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
#Query("select playlist from Playlist playlist
fetch join playlist.playlistItems itens
fetch join itens.video as video
where video.isDeleted = false")
List<Playlist> findAll();
}
Or even better way :
public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
#Query("select playlist from Playlist playlist
fetch join playlist.playlistItems itens
fetch join itens.video as video
where video.isDeleted = :hasVideo ")
List<Playlist> findPlayList(#Param("hasVideo") boolean hasVideo);
}
You can look into Spring Data Specifications. You use them by calling repository.findAll(s);
Specifications allow you add on arbitrary conditions to your query, including the filter you want to add. Another nice thing about Specifications is that they can be type-safe. See here:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications