/*
 * RESTHeart - the Web API for MongoDB
 * Copyright (C) SoftInstigate Srl
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.restheart.metadata.transformers;

import io.undertow.server.HttpServerExchange;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.mindrot.jbcrypt.BCrypt;
import org.restheart.handlers.RequestContext;
import org.restheart.handlers.RequestContext.TYPE;

/**
 *
 * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>}
 *
 * This transformer hashes the properties from the resource representation on
 * write requests using the bcrypt hash function the properties to hash out are
 * passed in the args argument as an array of strings. the bcrypt
 *
 * <br>Example that hashes the property 'password' from the response:
 * <br>{ rts: [{"name": "hashProperties", "phase":"REQUEST", "scope":"CHILDREN",
 * "args": { "props": ["password"], "complexity": 12 }}] }
 *
 */
public class HashTransformer implements Transformer {
    /**
     *
     * @param exchange
     * @param context
     * @param contentToTransform
     * @param args properties to filter out as an array of strings (["prop1",
     * "prop2"]
     */
    @Override
    public void transform(
            final HttpServerExchange exchange,
            final RequestContext context,
            BsonValue contentToTransform,
            final BsonValue args) {
        if (!doesApply(context) || contentToTransform == null) {
            // nothing to do
            return;
        }

        if (context.getType() == TYPE.DOCUMENT
                && (context.getMethod() == RequestContext.METHOD.PATCH
                || context.getMethod() == RequestContext.METHOD.PUT)) {

        }

        if (!contentToTransform.isDocument()) {
            throw new IllegalStateException(
                    "content to transform is not a document");
        }

        BsonDocument _contentToTransform = contentToTransform.asDocument();

        if (args.isDocument()) {
            BsonValue _tohash = args.asDocument().get("props");

            if (_tohash == null || !_tohash.isArray()) {
                context.addWarning("transformer wrong definition: "
                        + "args must be an object as {'props': [ 'password' ], "
                        + "'complexity': 12 }");
            }

            BsonArray tohash = _tohash.asArray();

            BsonValue _complexity = args.asDocument().get("complexity");

            if (_complexity != null && !_complexity.isNumber()) {
                context.addWarning("transformer wrong definition: "
                        + "args must be an object as {'props': [ 'password' ], "
                        + "'complexity': 12 }");
            }

            int complexity = _complexity == null
                    ? 12
                    : _complexity.asNumber().intValue();

            tohash.forEach(_prop -> {
                if (_prop.isString()) {
                    String prop = (String) _prop.asString().getValue();

                    BsonValue _value = _contentToTransform.get(prop);

                    if (_value != null && _value.isString()) {
                        String value = _value.asString().getValue();

                        _contentToTransform.replace(prop,
                                new BsonString(
                                        BCrypt.hashpw(value,
                                                BCrypt.gensalt(complexity))));

                    }
                } else {
                    context.addWarning("property in the args list "
                            + "is not a string: " + _prop);
                }
            });

        } else {
            context.addWarning("transformer wrong definition: "
                    + "args must be an object as {'props': [ 'password'], "
                    + "'complexity': 12 }");
        }
    }

    private boolean doesApply(RequestContext context) {
        return ( // PUT|PATCH /db/coll/docid
                (context.getType() == TYPE.DOCUMENT
                && (context.getMethod() == RequestContext.METHOD.PATCH
                || context.getMethod() == RequestContext.METHOD.PUT))
                // POST /db/coll { <doc> } and POST /db/coll [ { <doc> }, { <doc> } ]
                || ((context.getType() == TYPE.COLLECTION)
                && (context.getMethod() == RequestContext.METHOD.POST))
                // PATCH /db/coll/*
                || ((context.getType() == TYPE.BULK_DOCUMENTS)
                && context.getMethod() == RequestContext.METHOD.PATCH));
    }
}

@@ -1658,16 +1658,17 @@
 ormer %7B%0A
+%0A
     /**%0A
@@ -3031,16 +3031,82 @@
 tohash =
+ null;%0A            if (null != _tohash) %7B%0A                tohash =
  _tohash
@@ -3116,16 +3116,30 @@
 Array();
+%0A            %7D
 %0A%0A      
@@ -3609,32 +3609,70 @@
 ().intValue();%0A%0A
+            if (null != tohash) %7B%0A    
             toha
@@ -3693,16 +3693,20 @@
 op -%3E %7B%0A
+    
         
@@ -3729,32 +3729,36 @@
 p.isString()) %7B%0A
+    
                 
@@ -3778,17 +3778,8 @@
 op =
- (String)
  _pr
@@ -3797,32 +3797,36 @@
 ().getValue();%0A%0A
+    
                 
@@ -3896,24 +3896,28 @@
             
+  
+  
 if (_value !
@@ -3939,32 +3939,36 @@
 e.isString()) %7B%0A
+    
                 
@@ -4045,16 +4045,20 @@
         
+  
+  
 _content
@@ -4115,16 +4115,20 @@
         
+    
 new Bson
@@ -4171,24 +4171,28 @@
             
+ 
+   
 BCrypt.hashp
@@ -4200,16 +4200,20 @@
 (value,%0A
+    
         
@@ -4296,34 +4296,42 @@
                 
-%7D%0A
+    %7D%0A    
                 
@@ -4351,32 +4351,36 @@
                 
+    
 context.addWarni
@@ -4411,16 +4411,20 @@
  list %22%0A
+    
         
@@ -4483,34 +4483,41 @@
                 
-%7D%0A
+    %7D%0A   
             %7D);%0A
@@ -4516,11 +4516,26 @@
     
+ 
 %7D);
+%0A            %7D
 %0A%0A  

Add checks for null