I have a CRUD repository from this example of Spring Data. I'm trying to add custom permission evaluation, but in my implementation of PermissionEvalutor, targetDomainObject is always null.
ItemRepository
#PreAuthorize("hasRole('ROLE_USER')")
public interface ItemRepository extends CrudRepository<Item, Long> {
#PreAuthorize("hasPermission(#entity, 'DELETE')")
<S extends Item> S save(S entity);
#PreAuthorize("hasRole('ROLE_ADMIN')")
void delete(Long id);
}
Following the suggestions in the answers to this question of making the interface and implementation parameter names to match, I've tried changing entity by item in both the expression and the method parameter. I'm not sure what implementation should match against what interface here, so I'm guessing is SimpleJpaRepository against ItemRepository/CrudRepository. Anyway, it doesn't work, targetDomainObject is always null. Same for targetId in the other method.
Debugging MethodSecurityEvaluationContext.lookupVariable shows that args.length = 0, inside the method addArgumentsAsVariables(), that then logs Unable to resolve method parameter names for method: public abstract xx.xxx.Item xx.xxx.ItemRepository.save(xx.xxx.Item). Debug symbol information is required if you are using parameter names in expressions.. At lookupVariable, everything is null.
Is the debug symbol not #? What am I doing wrong?
Haven't looked in the actual code, but judging from what you write about the debug information, Spring isn't able to find the parameter names, probably since the come from interfaces and those aren't included in the bytecode by default.
Try adding a -parameters compiler flag. Also see this answer for a probably similar problem: https://stackoverflow.com/a/40787280
Related
I need to implement two different implementations for the same findAll() method by following different EntityGraphs annotations. By referencing through another StackOverflow post, I found a way to implement the same findAll() method with different EntityGrpahs. But when I use default methods as mentioned in that post, I am not getting the expected behavior. It neglects the #EntityGraph annotation and returns lazy Collections by following the default behavior.
Please provide a fix for this issue or state any other better solution that I can implement to solve this problem.
public interface BspCategoryRepository extends JpaRepository<DbpMetaBspCategory, String> {
#EntityGraph(attributePaths = {"dbpBspMetaCollection","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection","dbpBspMetaCollection.bspType","dbpBspMetaCollection.bankCode","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection.fieldType"}, type = EntityGraph.EntityGraphType.FETCH)
default List<DbpMetaBspCategory> findAllCategories(){
return findAll();
}
}
Please refer to the second answer in the post which was answered by Femi.
References
Spring Data simply can not know about this annotation, as the method is not abstract. You should be able to declare the method just like this:
#EntityGraph(attributePaths = {"dbpBspMetaCollection","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection","dbpBspMetaCollection.bspType","dbpBspMetaCollection.bankCode","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection.fieldType"}, type = EntityGraph.EntityGraphType.FETCH)
List<DbpMetaBspCategory> findAllCategories();
i'm adding lots of bold because someone downgraded my question which is think is strange...
i went from this which worked which means things are configured correctly for #PreAuthorize...
#RestController
#RequestMapping('/people')
public PersonController extends BaseController {
#PreAuthorize("#pesonId != principal.id")
#RequestMapping(value="updatePerson", method={RequestMethod.POST}, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody SimpleResponseStatus updatePerson(#RequestParam(personId) final Long personId, #RequestParam(value) final String value, final HttpServerRequest request, final HttpServletResponse response)
{
Person p = personRepo.findById(personId);
p.setValue(value);
personRepo.save(p);
}
}
and moved to this which doesn't work ... the #PreAuthorize in the Repository save()...
public interface PersonRepository extends JpaRepository<Person,Long> {
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("#p.id != principal.id")
Person save(person p);
}
and now i get a "Failed to evaluate expression '#p.id != principal.id'
One difference between when it was working on the Controller was i did #personId and not #p.id so i don't know if the object vs primitive in the expression is the problem or if Controller vs Repository (where i do the evaluation) is the problem.
So i have a few questions...
Do i have to do anything special to get the PreAuthorize working in the Repository?
Nothing to do with Spring security but why was i forced to add the SuppressWarnings? i can see if i was returning List<Person> maybe but i thought that was strange.
There is another instance where i will want to do an a PreAuthorize expression like "#p.group.id != 3" ... is there a limit to the levels that can be in an evaluation? i.e. level = obj.obj.obj.obj.value
Another interesting thing is that when i had it working with the Controller i didn't need curly braces "#{userId != 3}" but it worked with "#userId != 3" and i got that syntax from here.
Bottom line, i had it working in a Controller but without an object parameter and now i need it to work in a Repository and with an object parameter. And i've tried #person.id != 3 as well as #{person.id != 3} and neither work.
i found the answer to my own question: here
which is basically for the Repository you have to add a parameter name via annotation since the debug isn't compiled into the interface.
it took me a long time to finally find the answer as it took trial and error of me trying different EL syntax and finally one syntax i chose gave me a different (and better) error message and from there i found the link above.
Anyway, whoever downgraded my question should have just posted the link i just did above instead of downgrading me. that is just mean, really.
public interface PersonRepository extends JpaRepository<Person,Long> {
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("#p.id != principal.id")
Person save(#Param("p") person p); //the #Param annotation is needed!
}
also, it is interesting how some places i see {} are needed and other places not. i did not need braces for this to work.
you must first:
- enable global method security
in your spring security config just add
#EnableGlobalMethodSecurity(prePostEnabled=true)
the you can safely use #PreAuthorize and #PostAuthorize
correct SPEL syntax should be
#{pesonId != principal.id}
but...before this you should ensure both parameter are present in SPringEvaluationContext.
I suggest to use Spring approach, which doesn't fit you question but gives you a different point of view of the problem.
Usage of expression bases access control
https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
ex: #PreAuthorize("hasRole('ADMIN')")
Spring security accept any valid Spel inside annotation.
Take a look here:
http://www.baeldung.com/spring-security-expressions-basic
I was playing with the following question: Using Java 8's Optional with Stream::flatMap and wanted to add a method to a custom Optional<T> and then check if it worked.
More precise, I wanted to add a stream() to my CustomOptional<T> that returns an empty stream if no value is present, or a stream with a single element if it is present.
However, I came to the conclusion that Optional<T> is declared as final.
Why is this so? There are loads of classes that are not declared as final, and I personally do not see a reason here to declare Optional<T> final.
As a second question, why can not all methods be final, if the worry is that they would be overridden, and leave the class non-final?
According to this page of the Java SE 8 API docs, Optional<T> is a value based class. According to this page of the API docs, value-based classes have to be immutable.
Declaring all the methods in Optional<T> as final will prevent the methods from being overridden, but that will not prevent an extending class from adding fields and methods. Extending the class and adding a field together with a method that changes the value of that field would make that subclass mutable and hence would allow the creation of a mutable Optional<T>. The following is an example of such a subclass that could be created if Optional<T> would not be declared final.
//Example created by #assylias
public class Sub<T> extends Optional<T> {
private T t;
public void set(T t) {
this.t = t;
}
}
Declaring Optional<T> final prevents the creation of subclasses like the one above and hence guarantees Optional<T> to be always immutable.
As others have stated Optional is a value based class and since it is a value based class it should be immutable which needs it to be final.
But we missed the point for this. One of the main reason why value based classes are immutable is to guarantee thread safety. Making it immutable makes it thread safe. Take for eg String or primitive wrappers like Integer or Float. They are declared final for similar reasons.
Probably, the reason is the same as why String is final; that is, so that all users of the Optional class can be assured that the methods on the instance they receive keep to their contract of always returning the same value.
Though we could not extend the Optional class, we could create our own wrapper class.
public final class Opt {
private Opt() {
}
public static final <T> Stream<T> filledOrEmpty(T t) {
return Optional.ofNullable(t).isPresent() ? Stream.of(t) : Stream.empty();
}
}
Hope it might helps you. Glad to see the reaction!
This is my code :
public interface UserRepo extends CrudRepository<User, Long> {
boolean exist(Long id);
#Override
User save(User user);
}
In eclipse, there is a warning on the return type User.
Description Resource Path Location Type
Type safety: The return type User for save(User) from the type UserRepo needs unchecked conversion to conform to S from the type CrudRepository UserRepo.java
May I know
what is the reason ecplise warning on return type unchecked conversion?
what is the correct way to get rid of the warning?
TQ
As #dunny figured out in his comment, this statement makes no sense in the interface, as it is already implemented in org.springframework.data.repository.CrudRepository.save(S)
Eclipse gives this warning as it can not know, that the S in the super implementation is a User in this case.
In order to answer your 2. question, you can do a
#Override
<S extends User> S save(S user);
Then you get rid of the warning, but even then, it does not make more sense to provide this signature.
Just skip this statement, as it is already there.
I need to test a service class, but when I try to mock the dao class, it doesn't get triggered, thus not able to use ThenReturn().
I think that the problem is because I use an interface for my Dao and #Autowired in the service class (Spring MVC 3.1):
The interface:
public interface TestDao {
int createObject(Test test) throws NamingException;
}
The implementation:
#Repository
public class TestDaoImpl implements TestDao {
#Override
public int createObject(Test test) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new InsertNewTest(test), keyHolder);
return ((java.math.BigDecimal)keyHolder.getKey()).intValue();
}
}
The service:
public class RegTest {
#Autowired
TestDao testDao;
public int regTest(int .....) {
.
.
int cabotageId = testDao.createObject(test);
}
}
In the test I have:
#RunWith(MockitoJUnitRunner.class)
public class TestRegService {
#InjectMocks
private RegTest regTest = new RegTest();
#Mock
TestDao testDao;
#Test()
public void test() {
.
when(testDao.createObject(null)).thenReturn(100);
.
}
testDao.createObject(null) returns 0 (due to being mock'ed) and not 100 as I is trying to achieve.
Can anybody help, please?
Problem solved!
It was the passing test-object to createObject() that did not match. Using
testDao.createObject(any(Test.class))
did the trick!
If your test is actually passing a value to createObject, then when(testDao.createObject(null)... never gets matched. Rather than matching on null, you could match any instance of Test with testDao.createObject(any(Test.class))...
Also when you tried later to supply new Test() as the argument to match, it will literally try to match on that exact instance of Test, but presumably your real code is new-ing up a different one. So the use of Matchers.any(Test.class) as the parameter to match is the way to go.
Mockito injection mechanism don't know about Spring #Autowired or CDI #Inject annotations. It just tries to find the best candidate given the type and the name of the mock, and it can lookup private fields too. See the javadoc of #InjectMocks : http://docs.mockito.googlecode.com/hg/1.9.0/org/mockito/InjectMocks.html
The semantic you are using is correct, though if you are experiencing issues, I would rather look for incorrect interactions or incorrect arguments.
Are you sure the test variable in regTest.regTest(int...) is really null when passed to testDao.createObject(test) ?
I don't know if this is a typo in the example, but you have RegTest.regTest() calling createTest() rather than createObject(). Otherwise, I don't think #Autowired has anything to do with it, since your test itself is not running in a container with Spring management. If it is not a typo, and createTest is in fact a real and different method from createObject, then the default behaviour of a mocked object in Mockito is to return the appropriately-typed zero for numeric return types.
I think that you're right about the autowire not getting called. You could inject the dao yourself using the setTestDao() call instead. Mockito also supports spy which allows you to trace the objects code and just replace functions instead.