spring boot - mongodb - DBRef Hashset accepts duplicates - spring

I have a user entity with a set of favorite items. and i annotate the field as follows
class user {
#Id
private String id;
#DBREF
private HashSet <Item> favorites;
//empty and full constructor + getters and setters
}
when i want to add a new item to the set i get the user from the database, get his favorites insert new item and save user. for adding and removing items I use the add and remove functions of the Set interface.
the problem: the set accepts duplicates, and remove function does not remove from the set
EDIT: here is how I add an item to the set:
public void addItem(Strin username, String id){
Item item= itemService.findById(id);
User user = userService.findByUsername(username);
HashSet<Item> set= user.getFavorites();
set.add(Item);
user.setFavorites(set);
this.userRepository.save(user);
}

For future reference, I had to correctly implement the equals function of class Item:
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Item)) {
return false;
}
Item item = (Item) o;
// wrong (for comparing Strings): return id == item.getId()
// right:
return id.equals(item.getId());
}
#Override
public int hashCode() {
return Objects.hash(id);
}

Related

How to achieve row level authorization in spring-boot?

Assuming I've the following endpoints in spring boot
GET /todo
DELETE /todo/{id}
How can ensure that only entries for the userid are returned and that the user can only update his own todos?
I've a populated Authentication object.
Is there any build in way I can use? Or just make sure to always call findXyzByIdAndUserId where userid is always retrieved from the Principal?
I'm a bit worried about the possibility to forget the check and displaying entries from other users.
My approach to this would be a 3 way implementation: (using jpa & hibernate)
a user request context
a mapped superclass to get your context
a statement inspector to inject your userid
For example:
public final class UserRequestContext {
public static String getUserId() {
// code to retrieve your userid and throw when there is none!
if (userId == null) throw new IllegalStateException("userid null");
return userId;
}
}
#MappedSuperclass
public class UserResolver {
public static final String USER_RESOLVER = "USER_RESOLVER";
#Access(AccessType.PROPERTY)
public String getUserId() {
return UserRequestContext.getUserId();
}
}
#Component
public class UserInspector implements StatementInspector {
#Override
public String inspect(String statement) {
if (statement.contains(UserResolver.USER_RESOLVER)) {
statement = statement.replace(UserResolver.USER_RESOLVER, "userId = '" + UserRequestContext.getUserId() + "'" );
}
return sql;
}
#Bean
public HibernatePropertyCustomizer hibernatePropertyCustomizer() {
return hibernateProperies -> hibernateProperties.put("hibernate.session_factory.statement_inspector",
UserInspector.class.getName());
}
}
So your Entity looks like this:
#Entity
...
#Where(clause = UserResolver.USER_RESOLVER)
public class Todo extends UserResolver {
...
}

Unexpected return value

I'm trying to implement password comparison. First I tried this:
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private OldPasswordsService oldPasswordsService;
Optional<OldPasswords> list = oldPasswordsService.findEncryptedPassword(passwordEncoder.encode("new password entered form web reset form"));
OldPasswords value = list.get();
boolean matches = passwordEncoder.matches("new password entered form web reset form", value.getEncryptedPassword());
if (matches)
{
return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
}
else
{
OldPasswords oldPasswords = new OldPasswords();
oldPasswords.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
oldPasswords.setCreatedAt(LocalDateTime.now());
oldPasswordsService.save(oldPasswords);
}
Table for old passwords:
#Table(name = "old_passwords")
public class OldPasswords implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private int id;
#Column(name = "encrypted_password", length = 255)
private String encryptedPassword;
I tried to implement this:
......
return this.userService.findByLogin(resetDTO.getName()).map(user -> {
Optional<OldPasswords> list = oldPasswordsService.findEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
list.ifPresent(value -> {
boolean matches = passwordEncoder.matches(resetDTO.getPassword(), value.getEncryptedPassword());
if (matches) {
return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
}
}).orElse(() -> {
OldPasswords oldPasswords = new OldPasswords();
oldPasswords.setEncryptedPassword(passwordEncoder.encode(resetDTO.getPassword()));
oldPasswordsService.save(oldPasswords);
});
return ok().build();
}).orElseGet(() -> notFound().build());
}
But I get for this line:
return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
Error
Unexpected return value
Do you know how I can fix this?
It is probably List!
So with:
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private OldPasswordsService oldPasswordsService;
...and
#Autowired
private UserXXXService userService;
...let's assume, you are trying to actually:
#RequestMapping(...)
public ResponseEntity<String> resetPassword(ResetPasswordDTO dto) {
...
...that's how i would probalby go about it:
final Optional<User> user = this.userService.findByLogin(dto.getName()); // Optional or null?, let's assume Optional
// if user (login) exists:
if(user.isPresent()) {
// check old passwords, the method name/data structure lets assume it's rather List than Optional:
java.util.List<OldPasswords> list = oldPasswordsService.findEncryptedPassword(passwordEncoder.encode(dto.getPassword()));
if(list.isEmpty()) {// list is empty...
// do your things..
OldPasswords oP= new OldPasswords();
oP.setEncryptedPassword(passwordEncoder.encode(dto.getPassword()));
oldPasswordsService.save(oP);
return ResonseEntity.ok().build(); // ok!
} else {// otherwise:
return new ResponseEntity<>("PASSWORD_ALREADY_USED", HttpStatus.BAD_REQUEST);
}
} else { // otherwise (user.login not exists)
return ResponseEntity<>.notFound().build();
}
}
(nor tested nor compiled)
..one technical question/detail remains/hidden: I miss the "binding" of "user" and "old_password"... so, should it check the old passwords of one user or all?
The 1st one sounds more fair/correct: old passwords should be "user based":
#Entity
// some unique constraints, when you have..., would be nice
public class OldPasswords implements Serializable { // singular is better for entity/table names!
....
#ManyToOne
#JoinColumn("user_id") // if you don't want to map the entity (for some reasons), you should still map the "id".
private User user;
// getter/setter ...
}
...
(with least impact,) you could then:
public interface OldPasswordsRepository extends Repository<OldPasswords, Integer> { // <- Integer ???
List<OldPasswords> findByUserAndEncryptedPassword(User user, String pwd); // to use that...
}
EDIT: To match against the last three passwords, #Peter, your datastructure needs (additionally to user) some "tweak" - like an created timestamp!;) (a better choice, than a "version/age column")
#Column(nullable = false) // final + instantiation is suited here
final Date created = new Date(); //long, LocalDateTime, DateTime... Timestamp, java.sql.Date... and many alternatives.
// getter
..then you could (in the repository):
// untested!
List<OldPasswords> findTop3ByUserOrderByCreatedDesc(User user);
..and use it in the controller/service, like:
... // if user is present
List<OldPasswords> list = oldPasswordsRpository.findTop3ByUserOrderByCreatedDesc(user);
for(OldPasswords opw:list) {
// compare opw to dto.getPasword if match: return "bad request"
}
// after that (no bad request), store new (& old) password... (everywhere relevant),
// ...and when nothing fails:
return ResponseEntity.ok().build();
// else: user not present -> return not found

Caching with Spring and ehcache doesnt work as expected

I have a Product model object like this -
class ProductDTO {
int id;
String code;
String description;
//getters and setters go here
}
I am writing a service (code below) that looks up products by id or code and returns their description. I am using Spring 4 and ehcache to cache the results.
I have 2 methods - one for lookup by id and one for lookup by code - they are getProductByCode and getProductById. Both return the description as a string. They do so by calling getAllProducts() which returns a list of all products. The callers then search the list for a product matching the id or code and return the description.
getAllProducts() also calls 2 methods with #CachePut for each product - to save the description Strings in cache - by key code and id.
Caching works properly if the same arguments are passed for code or id to to the getProductByCode and getProductById methods. But if I pass a different argument, getAllProducts() is called again.
How do I achieve the desired behavior - where every time a call is made to getAllProducts(), all descriptions get cached and a subsequent call looks up the cache rather than going to the repository?
public class ProductServiceImpl implements ProductService {
#Autowired
ProductsRepository ProductRepo;
#Override
public List<ProductDTO> getAllProducts() {
List<ProductDTO> products = ProductRepo.getAllProducts();
for(ProductDTO prodDTO : products) {
String desc = prodDTO.getDescription();
String code = prodDTO.getCode();
int id = prodDTO.getId();
putDescriptionInCache(desc, code);
putDescriptionInCache(desc, id);
}
return products;
}
#CachePut(value = "products", key = "#id")
public String putDescriptionInCache(String description, int id){
return description;
}
#CachePut(value = "products", key = "#code")
public String putDescriptionInCache(String description, String code){
return description;
}
#Override
#Cacheable(value="products", key="#id")
public String getProductById(Integer id) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
int currId = currDTO.getId();
if(id.equals(new Integer(currId))) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
#Override
#Cacheable(value="products", key="#code")
public String getProductByCode(String code) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
String currCode = currDTO.getCode();
if(currCode.equals(code)) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
}
As it was commented by M. Deinum, the problem comes from the annotations, like CachePut or Cacheable, being transformed into an aspect at runtime. And the main limitation with that approach is that calls from the same class are not properly captured.
As you replied yourself in the comments section, moving the annotated methods to another type that is injected in the current one solves the problem.

Neo4j eager/lazy loading with Spring data

I'm investigating Neo4j and have a question with regards to object eager/lazy loading. Lets say I have class Trolley with has Set<Item> (with getters/setters). If I do the following:
Trolley t = new Trolley(...); // create empty trolley
t.addItem(f); // add one item to the trolley
t.persist(); // persist the object
I then later find the trolley based on the nodeId:
repo.findOne(xxx); // returns the trolley successfully
When I do something like:
trolley.getItems().size()
this is empty. I guess this is the intended behaviour. Is there any mechanism similar to JPA where is the session/tx is open to load the collection dynamically.
Code:
#NodeEntity
public class Trolley
{
#Indexed
private String name;
#RelatedTo
private Set<Item> items;
public Trolley(){}
public Trolley(String name)
{
this.name = name;
}
public void addItem(Item item)
{
this.items.add(item);
}
public Set<Item> getItems()
{
return items;
}
}
#NodeEntity
public class Item
{
private String name;
public Item(){}
public Item(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
}
#Test
public void trolleyWithItemPersist()
{
Trolley trolley = new Trolley("trolley1").persist();
// Persisting - however I would've expected a cascade to
// occur when adding to the collection.
Item item = new Item("item1").persist();
// now add to the trolley
trolley.addItem(item);
// persist
trolley.persist();
// Now use repo to get trolley
Trolley loadedTrolley = trolleyRepository.findOne(trolley.getNodeId());
// should have one item
assertEquals(1, loadedTrolley.getItems().size());
}
Afaik, in Spring Data Jpa, to populate an lazy loaded field you need to annotate the method which call the findOne(xxx) with
#Transactional
from (org.springframework.transaction.annotation.Transactional)
Maybe it works also with neo4j...
I'm not really an skilled developper on Spring Data but this is the only way I know to retrieve lazy loaded fields. If someone has a better solution, feel free to write it!

Spring: escaping input when binding to command

How do you handle the case where you want user input from a form to be htmlEscape'd when
you are binding to a command object?
I want this to sanitize input data automatically in order to avoid running through all fields in command object.
thanks.
If you are using a FormController you can register a new property editor by overriding the initBinder(HttpServletReques, ServletRequestDataBinder) method. This property editor can escape the html, javascript and sql injection.
If you are using a property editor the values from the request object will be processed by the editor before assigning to the command object.
When we register a editor we have to specify the type of the item whose values has to be processed by the editor.
Sorry, now I don't the syntax of the method. But I'm sure this is how we have achieved this.
EDITED
I think the following syntax can work
In your controller override the following method as shown
#Override
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
super.initBinder(request, binder);
binder.registerCustomEditor(String.class,
new StringEscapeEditor(true, true, false));
}
Then create the following property editor
public class StringEscapeEditor extends PropertyEditorSupport {
private boolean escapeHTML;
private boolean escapeJavaScript;
private boolean escapeSQL;
public StringEscapeEditor() {
super();
}
public StringEscapeEditor(boolean escapeHTML, boolean escapeJavaScript,
boolean escapeSQL) {
super();
this.escapeHTML = escapeHTML;
this.escapeJavaScript = escapeJavaScript;
this.escapeSQL = escapeSQL;
}
public void setAsText(String text) {
if (text == null) {
setValue(null);
} else {
String value = text;
if (escapeHTML) {
value = StringEscapeUtils.escapeHtml(value);
}
if (escapeJavaScript) {
value = StringEscapeUtils.escapeJavaScript(value);
}
if (escapeSQL) {
value = StringEscapeUtils.escapeSql(value);
}
setValue(value);
}
}
public String getAsText() {
Object value = getValue();
return (value != null ? value.toString() : "");
}
}
Hopes this helps you
You can use #Valid and #SafeHtml from hibernate validator. See details at https://stackoverflow.com/a/40644276/548473

Resources