_binary From 9476a0272aaf59935ab70dde5b17a15d0f185e7e Mon Sep 17 00:00:00 2001
From: Pragya Rana <pragya.rana9@gmail.com>
Date: Thu, 30 Mar 2017 17:13:56 -0700
Subject: [PATCH] WIP: Deliverable 2

---
 src/configs/Config.php      |   7 +++
 src/configs/Createdb.php    |  28 ++++++++++
 src/library/AES.php         | 125 ++++++++++++++++++++++++++++++++++++++++++++
 src/models/GroupModel.php   |  61 +++++++++++++++++++++
 src/models/Model.php        |  12 +++++
 src/models/ProfileModel.php |  35 ++++++++++++-
 6 files changed, 266 insertions(+), 2 deletions(-)
 create mode 100644 src/library/AES.php

diff --git a/src/configs/Config.php b/src/configs/Config.php
index efafbb75..b6c9b11d 100755
--- a/src/configs/Config.php
+++ b/src/configs/Config.php
@@ -333,6 +333,7 @@ if (file_exists(WORK_DIRECTORY . PROFILE_FILE_NAME)) {
     /** @ignore */
     nsdefine('PROFILE', false);
     nsdefine('DBMS', 'Sqlite3');
+    nsdefine('PRIVATE_DBMS', 'Sqlite3');
     nsdefine('AUTHENTICATION_MODE', NORMAL_AUTHENTICATION);
     nsdefine('RECOVERY_MODE', EMAIL_RECOVERY);
     nsdefine('DEBUG_LEVEL', NO_DEBUG_INFO);
@@ -350,8 +351,12 @@ if (file_exists(WORK_DIRECTORY . PROFILE_FILE_NAME)) {
     nsdefine('MEDIA_MODE', 'name_server');
     nsdefine('DB_NAME', "default");
     nsdefine('DB_USER', '');
+    nsdefine('PRIVATE_DB_USER', '');
     nsdefine('DB_PASSWORD', '');
+    nsdefine('PRIVATE_DB_PASSWORD', '');
     nsdefine('DB_HOST', '');
+    nsdefine('PRIVATE_DB_HOST', '');
+    nsdefine('PRIVATE_DB_NAME', "default2");
     /** @ignore */
     nsdefine('CRAWL_DIR', BASE_DIR);
     /** @ignore */
@@ -942,3 +947,5 @@ nsconddefine('AD_DATE_FORMAT','Y-m-d');
 nsconddefine('AD_LOGO','resources/adv-logo.png');
 /** sentence compression enabled or not*/
 nsconddefine('SENTENCE_COMPRESSION_ENABLED', false);
+/** Define cipher to be used in AES */
+nsconddefine('AES_256_CBC', 'aes-256-cbc');
diff --git a/src/configs/Createdb.php b/src/configs/Createdb.php
index 2dda9d0c..edb15636 100755
--- a/src/configs/Createdb.php
+++ b/src/configs/Createdb.php
@@ -35,6 +35,7 @@
 namespace seekquarry\yioop\configs;
 
 use seekquarry\yioop\library as L;
+use seekquarry\yioop\library\AES;
 use seekquarry\yioop\models\Model;
 use seekquarry\yioop\models\ProfileModel;
 use seekquarry\yioop\models\GroupModel;
@@ -50,33 +51,55 @@ require_once __DIR__."/../library/LocaleFunctions.php";
 /** To make it easy to insert translations */
 require_once __DIR__."/../library/UpgradeFunctions.php";
 $profile_model = new ProfileModel(DB_NAME, false);
+$private_profile_model = new ProfileModel(PRIVATE_DB_NAME, false);
 $db_class = NS_DATASOURCES . ucfirst(DBMS)."Manager";
+$private_db_class = NS_DATASOURCES . ucfirst(PRIVATE_DBMS)."Manager";
 $dbinfo = ["DBMS" => DBMS, "DB_HOST" => DB_HOST, "DB_USER" => DB_USER,
     "DB_PASSWORD" => DB_PASSWORD, "DB_NAME" => DB_NAME];
+$private_dbinfo = ["DBMS" => PRIVATE_DBMS, "DB_HOST" => PRIVATE_DB_HOST, "DB_USER" => PRIVATE_DB_USER,
+    "DB_PASSWORD" => PRIVATE_DB_PASSWORD, "DB_NAME" => PRIVATE_DB_NAME];
 if (!in_array(DBMS, ['sqlite', 'sqlite3'])) {
     $db = new $db_class();
+    $private_db = new $private_db_class();
     $db->connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
+    $private_db->connect(PRIVATE_DB_HOST, PRIVATE_DB_USER, PRIVATE_DB_PASSWORD, PRIVATE_DB_NAME);
     /*  postgres doesn't let you drop a database while connected to it so drop
         tables instead first
      */
     $profile_model->initializeSql($db, $dbinfo);
+    $private_profile_model->initializeSqlPrivate($private_db, $private_dbinfo);
     $database_tables = array_keys($profile_model->create_statements);
+    $private_database_tables = array_keys($private_profile_model->private_create_statements);
     foreach ($database_tables as $table) {
         $db->execute("DROP TABLE ".$table);
     }
+    foreach ($private_database_tables as $table) {
+        $private_db->execute("DROP TABLE ".$table);
+    }
     $db->execute("DROP DATABASE IF EXISTS " . DB_NAME);
     $db->execute("CREATE DATABASE " . DB_NAME);
     $db->disconnect();
     $db->connect(); // default connection goes to actual DB
+    $private_db->execute("DROP DATABASE IF EXISTS ". PRIVATE_DB_NAME);
+    $private_db->execute("CREATE DATABASE ". PRIVATE_DB_NAME);
+    $private_db->disconnect();
+    $private_db->connect(PRIVATE_DB_HOST, PRIVATE_DB_USER, PRIVATE_DB_PASSWORD, PRIVATE_DB_NAME);
 } else {
     @unlink(CRAWL_DIR."/data/" . DB_NAME . ".db");
     $db = new $db_class();
     $db->connect();
+    @unlink(CRAWL_DIR."/data/" . PRIVATE_DB_NAME . ".db");
+    $private_db = new $private_db_class();
+    $private_db->connect(PRIVATE_DB_HOST, PRIVATE_DB_USER, PRIVATE_DB_PASSWORD, PRIVATE_DB_NAME);
 }
 if (!$profile_model->createDatabaseTables($db, $dbinfo)) {
     echo "\n\nCouldn't create database tables!!!\n\n";
     exit();
 }
+if (!$private_profile_model->createDatabaseTablesPrivate($private_db, $private_dbinfo)) {
+    echo "\n\nCouldn't create database tables!!!\n\n";
+    exit();
+}
 $db->execute("INSERT INTO VERSION VALUES (" . YIOOP_VERSION . ")");
 $creation_time = L\microTimestamp();
 //numerical value of the blank password
@@ -136,6 +159,9 @@ $db->execute("INSERT INTO USER_GROUP VALUES (" . ROOT_ID . ", " .
     HELP_GROUP_ID . ", " . ACTIVE_STATUS . ", $now)");
 $db->execute("INSERT INTO USER_GROUP VALUES (" . PUBLIC_USER_ID . ", " .
     HELP_GROUP_ID . ", " . ACTIVE_STATUS . ", $now)");
+$private_db->execute("INSERT INTO TYPE_KEYS (TYPE_ID, KEY_NAME) VALUES(".PUBLIC_GROUP_ID.", \"".openssl_random_pseudo_bytes(32)."\")");
+$key = openssl_random_pseudo_bytes(32);
+$private_db->execute("INSERT INTO TYPE_KEYS (TYPE_ID, KEY_NAME) VALUES(".HELP_GROUP_ID.", \"".openssl_random_pseudo_bytes(32)."\")");
 $group_model = new GroupModel(DB_NAME, false);
 $group_model->db = $db;
 // Insert Default Public Wiki Pages
@@ -540,7 +566,9 @@ if (stristr(DB_HOST, "pgsql") !== false) {
 }
 
 $db->disconnect();
+$private_db->disconnect();
 if (in_array(DBMS, ['sqlite','sqlite3'])){
     chmod(CRAWL_DIR."/data/".DB_NAME.".db", 0666);
+    chmod(CRAWL_DIR."/data/".PRIVATE_DB_NAME.".db", 0666);
 }
 echo "Create DB succeeded\n";
diff --git a/src/library/AES.php b/src/library/AES.php
new file mode 100644
index 00000000..2ba0581c
--- /dev/null
+++ b/src/library/AES.php
@@ -0,0 +1,125 @@
+<?php
+namespace seekquarry\yioop\library;
+
+use seekquarry\yioop\configs as C;
+/**
+Aes encryption
+*/
+class AES {
+  protected $key;
+  protected $data;
+  protected $mode;
+  protected $IV;
+
+/**
+*
+* @param type $data
+* @param type $key
+* @param type $mode
+*/
+  function __construct($data = null, $mode = null, $key = null) {
+    $this->setData($data);
+    $this->setKey($key);
+    $this->setIV("");
+  }
+
+/**
+*
+* @param type $data
+*/
+  public function setData($data) {
+    $this->data = $data;
+  }
+
+/**
+*
+* @return type
+*/
+  protected function getData() {
+    return $this->data;
+  }
+
+/**
+*
+* @param type $key
+*/
+  public function setKey($key) {
+    $this->key = $key;
+  }
+
+/**
+*
+* @return type
+*/
+  protected function getKey() {
+    return $this->key;
+  }
+
+/**
+*
+* @return boolean
+*/
+  public function validateParams() {
+    if ($this->data != null &&
+        $this->key != null) {
+      return true;
+    } else {
+      return FALSE;
+    }
+  }
+
+/**
+*
+* @param type $IV
+*/
+  public function setIV($IV) {
+        $this->IV = $IV;
+    }
+
+/**
+*
+* @return type
+*/
+  protected function getIV() {
+      if ($this->IV == "") {
+        $this->IV = openssl_random_pseudo_bytes(openssl_cipher_iv_length(C\AES_256_CBC));
+      }
+      return $this->IV;
+  }
+
+/**
+* @return type
+* @throws Exception
+*/
+  public function encrypt() {
+    if ($this->validateParams()) {
+      $encrypted = openssl_encrypt($this->getData(), C\AES_256_CBC, $this->getKey(), 0, $this->getIV());
+      // append value of $IV to the encrypted data with a separator so this can be used during decryption
+      $encrypted = $this->getIV() .  str_repeat("0", 10)  . $encrypted;
+      return $encrypted;
+    } else {
+      throw new Exception('Invlid params!');
+    }
+  }
+
+/**
+*
+* @return type
+* @throws Exception
+*/
+  public function decrypt() {
+    if ($this->validateParams()) {
+      $pos = strpos($this->getData(), str_repeat("0", 10));
+      if ($pos !== false) {
+        $data_parts = explode(str_repeat("0", 10), $this->getData());
+      } else {
+        throw new Exception('Could not split keys');
+      }
+      $decrypted = openssl_decrypt($data_parts[1], C\AES_256_CBC, $this->getKey(), 0, $data_parts[0]);
+      return $decrypted;
+    } else {
+      throw new Exception('Invlid params!');
+    }
+  }
+}
+?>
\ No newline at end of file
diff --git a/src/models/GroupModel.php b/src/models/GroupModel.php
index cf00ed4e..6f9643f3 100644
--- a/src/models/GroupModel.php
+++ b/src/models/GroupModel.php
@@ -33,11 +33,13 @@ namespace seekquarry\yioop\models;
 use seekquarry\yioop as B;
 use seekquarry\yioop\configs as C;
 use seekquarry\yioop\library as L;
+use seekquarry\yioop\library\AES;
 use seekquarry\yioop\library\MediaConstants;
 use seekquarry\yioop\library\WikiParser;
 use seekquarry\yioop\library\processors\ImageProcessor;
 use seekquarry\yioop\models\ImpressionModel;
 
+
 /**
  * This is class is used to handle
  * db results related to Group Administration. Groups are collections of
@@ -246,6 +248,7 @@ class GroupModel extends Model implements MediaConstants
         $post_lifetime = C\FOREVER)
     {
         $db = $this->db;
+        $private_db = $this->private_db;
         $timestamp = L\microTimestamp();
         $sql = "INSERT INTO GROUPS (GROUP_NAME, CREATED_TIME, OWNER_ID,
             REGISTER_TYPE, MEMBER_ACCESS, VOTE_ACCESS, POST_LIFETIME)
@@ -268,6 +271,9 @@ class GroupModel extends Model implements MediaConstants
             $db);
         ImpressionModel::initWithDb(C\PUBLIC_GROUP_ID, $last_id,
             C\GROUP_IMPRESSION, $db);
+        $sql = "INSERT INTO TYPE_KEYS (TYPE_ID, KEY_NAME) VALUES (?, ?);";
+        $encrypt_key = openssl_random_pseudo_bytes(32);
+        $private_db->execute($sql, [$last_id, $encrypt_key]);
         return $last_id;
     }
     /**
@@ -385,6 +391,7 @@ class GroupModel extends Model implements MediaConstants
     public function deleteGroup($group_id)
     {
         $db = $this->db;
+        $private_db = $this->private_db;
         $params = [$group_id];
         $sql = "DELETE FROM GROUPS WHERE GROUP_ID=?";
         $db->execute($sql, $params);
@@ -394,6 +401,8 @@ class GroupModel extends Model implements MediaConstants
         $db->execute($sql, $params);
         $sql = "DELETE FROM GROUP_PAGE_HISTORY WHERE GROUP_ID=?";
         $db->execute($sql, $params);
+        $sql = "DELETE FROM TYPE_KEYS WHERE TYPE_ID=?";
+        $private_db->execute($sql, $params);
     }
     /**
      * Return the type of the registration for a group given by $group_id
@@ -567,6 +576,22 @@ class GroupModel extends Model implements MediaConstants
         return $row['NUM'];
     }
     /**
+     * Get the key of the group.
+     *
+     * @param int $group_id to get key for
+     * @return string key of the group
+     */
+    public function getGroupKey($group_id)
+    {
+        $private_db = $this->private_db;
+        $sql = "SELECT KEY_NAME FROM TYPE_KEYS WHERE TYPE_ID=?";
+        $result = $private_db->execute($sql, [$group_id]);
+        if ($result) {
+            $row = $private_db->fetchArray($result);
+        }
+        return $row['KEY_NAME'];
+    }
+    /**
      * To update the OWNER_ID of a group
      *
      * @param string $user_id the id of the user who becomes the admin of group
@@ -643,6 +668,12 @@ class GroupModel extends Model implements MediaConstants
             return false;
         }
         $row = $db->fetchArray($result);
+        if ($row['GROUP_ID'] != 2 && $row['GROUP_ID'] != 3) {
+            // Decrypt group's title and description
+            $key = $this->getGroupKey($row['GROUP_ID']);
+            $row['TITLE'] = $this->aes($row['TITLE'], $key, 1);
+            $row['DESCRIPTION'] = $this->aes($row['DESCRIPTION'], $key, 1);
+        }
         return $row;
     }
     /**
@@ -718,6 +749,11 @@ class GroupModel extends Model implements MediaConstants
         if ($post_time == 0) {
             $post_time = time();
         }
+        if ($group_id != 2 && $group_id != 3) {
+            $keys = $this->getGroupKey($group_id);
+            $title = $this->aes($title, $keys);
+            $description = $this->aes($description, $keys);            
+        }
         $sql = "INSERT INTO GROUP_ITEM (PARENT_ID, GROUP_ID, USER_ID, TITLE,
             DESCRIPTION, PUBDATE, EDIT_DATE, TYPE) VALUES
             (?, ?, ?, ?, ?, ?, ?, ?)";
@@ -732,6 +768,16 @@ class GroupModel extends Model implements MediaConstants
         }
         return $id;
     }
+    public function aes($data, $key, $type = 0)
+    {
+        $aes = new AES($data, C\AES_256_CBC, $key);
+        if ($type == 0) {
+            $data = $aes->encrypt();
+        } else {
+            $data = $aes->decrypt();
+        }
+        return $data;
+    }
     /**
      * Updates a group feed item's title and description. This assumes
      * the given item already exists.
@@ -744,6 +790,12 @@ class GroupModel extends Model implements MediaConstants
     {
         $db = $this->db;
         $edit_date = time();
+        $group_item = $this->getGroupItem($id);
+        if ($group_item['GROUP_ID'] != 2 && $group_item['GROUP_ID'] != 3) {        
+            $keys = $this->getGroupKey($group_item['GROUP_ID']);
+            $title = $this->aes($title, $keys);
+            $description = $this->aes($description, $keys);
+        }
         $sql = "UPDATE GROUP_ITEM SET TITLE=?, DESCRIPTION=?,
             EDIT_DATE=? WHERE ID=?";
         $db->execute($sql, [$title, $description, $edit_date, $id]);
@@ -895,6 +947,15 @@ class GroupModel extends Model implements MediaConstants
             }
         }
         unset($groups[$i]); //last one will be null
+        $i = 0;
+        foreach ($groups as $group_key => $group_value) {
+            if (($group_value['GROUP_ID'] != 2) && ($group_value['GROUP_ID'] != 3)) {
+                $keys = $this->getGroupKey($group_value['GROUP_ID']);
+                $groups[$i]['TITLE'] = $this->aes($group_value['TITLE'], $keys, 1);
+                $groups[$i]['DESCRIPTION'] = $this->aes($group_value['DESCRIPTION'], $keys, 1);
+                $i++;
+            }
+        }
         return $groups;
     }
     /**
diff --git a/src/models/Model.php b/src/models/Model.php
index 17f2d9b1..fde75f6d 100755
--- a/src/models/Model.php
+++ b/src/models/Model.php
@@ -67,6 +67,14 @@ class Model implements CrawlConstants
      * @var string
      */
     public $db_name;
+    /** Reference to a private DatasourceManager
+     * @var object
+     */
+    public $private_db;
+    /** Name of the private search engine database
+     * @var string
+     */
+    public $private_db_name;
     /**
      * Associative array of page summaries which might be used to
      * override default page summaries if set.
@@ -102,11 +110,15 @@ class Model implements CrawlConstants
     public function __construct($db_name = C\DB_NAME, $connect = true)
     {
         $db_class = C\NS_DATASOURCES . ucfirst(C\DBMS). "Manager";
+        $private_db_class = C\NS_DATASOURCES . ucfirst(C\PRIVATE_DBMS)."Manager";
         $this->db = new $db_class();
+        $this->private_db = new $private_db_class();
         if ($connect) {
             $this->db->connect();
+            $this->private_db->connect(C\PRIVATE_DB_HOST, C\PRIVATE_DB_USER, C\PRIVATE_DB_PASSWORD, C\PRIVATE_DB_NAME);
         }
         $this->db_name = $db_name;
+        $this->private_db_name = C\PRIVATE_DB_NAME;
     }
     /**
      * Given an array page summaries, for each summary extracts snippets which
diff --git a/src/models/ProfileModel.php b/src/models/ProfileModel.php
index ba988af0..ee7bc3a9 100755
--- a/src/models/ProfileModel.php
+++ b/src/models/ProfileModel.php
@@ -60,7 +60,7 @@ class ProfileModel extends Model
         'LINK_WEIGHT', 'LOGO', 'M_LOGO', 'MAIL_PASSWORD',  'MAIL_SECURITY',
         'MAIL_SENDER', 'MAIL_SERVER', 'MAIL_SERVERPORT', 'MAIL_USERNAME',
         'MEMCACHE_SERVERS', 'MIN_RESULTS_TO_GROUP', 'NAME_SERVER',
-        'MEDIA_MODE',
+        'MEDIA_MODE', 'PRIVATE_DB_NAME', 'PRIVATE_DB_HOST', 'PRIVATE_DBMS', 'PRIVATE_DB_PASSWORD', 'PRIVATE_DB_USER',
         'PROXY_SERVERS', 'RECOVERY_MODE', 'REGISTRATION_TYPE', 'RESULT_SCORE',
         'ROBOT_INSTANCE','RSS_ACCESS',
         'SEARCH_ANALYTICS_MODE', 'SEARCHBAR_PATH',
@@ -83,6 +83,7 @@ class ProfileModel extends Model
      * @var array
      */
     public $create_statements;
+    public $private_create_statements;
     /**
      * {@inheritDoc}
      *
@@ -94,6 +95,7 @@ class ProfileModel extends Model
     {
         parent::__construct($db_name, $connect);
         $this->create_statements = [];
+        $this->private_create_statements = [];
     }
     /**
      * Used to construct $this->create_statements, the list of all SQL
@@ -331,6 +333,20 @@ class ProfileModel extends Model
             "VERSION" => "CREATE TABLE VERSION(ID INTEGER PRIMARY KEY)",
             ];
     }
+    public function initializeSqlPrivate($dbm, $dbinfo)
+    {
+        $auto_increment = $dbm->autoIncrement($dbinfo);
+        $serial = $dbm->serialType($dbinfo);
+        /**
+         * SQL statements used to create the Yioop database. Some of the
+         * these statements could use UNIQUE on some the columns that are
+         * later used in CREATE INDEX statements. However, because of
+         * restrictions on the number of bytes (not chars) in MYSQL for keys
+         * this has not been done.
+         */
+        $this->private_create_statements = ["TYPE_KEYS" =>
+            "CREATE TABLE TYPE_KEYS (KEY_ID $serial PRIMARY KEY $auto_increment, TYPE_ID INTEGER, KEY_NAME VARCHAR(30))"];
+    }
     /**
      * Creates a folder to be used to maintain local information about this
      * instance of the Yioop/SeekQuarry engine
@@ -615,7 +631,7 @@ EOT;
      */
     public function createDatabaseTables($dbm, $dbinfo, $skip_list = [])
     {
-        $this->initializeSQL($dbm, $dbinfo);
+        $this->initializeSql($dbm, $dbinfo);
         $create_statements = $this->create_statements;
         foreach ($create_statements as $table_or_index => $statement) {
             if (in_array($table_or_index, $skip_list)) {
@@ -628,6 +644,21 @@ EOT;
         }
         return true;
     }
+    public function createDatabaseTablesPrivate($dbm, $dbinfo, $skip_list = [])
+    {
+        $this->initializeSqlPrivate($dbm, $dbinfo);
+        $private_create_statements = $this->private_create_statements;
+        foreach ($private_create_statements as $table_or_index => $statement) {
+            if (in_array($table_or_index, $skip_list)) {
+                continue;
+            }
+            if (!$result = $dbm->execute($statement)) {
+                echo $statement." ERROR!";
+                return false;
+            }
+        }
+        return true;
+    }
     /**
      * Checks if $dbinfo provides info to connect to an working instance of
      * app db.
-- 
2.11.0 (Apple Git-81)

