diff --git a/INSTALL/MYSQL.sql b/INSTALL/MYSQL.sql index b429d101a..80beb55ac 100644 --- a/INSTALL/MYSQL.sql +++ b/INSTALL/MYSQL.sql @@ -179,7 +179,7 @@ CREATE TABLE IF NOT EXISTS `event_blacklists` ( -- Table structure for `event_locks` -- -CREATE TABLE IF NOT EXISTS event_locks ( +CREATE TABLE IF NOT EXISTS `event_locks` ( `id` int(11) NOT NULL AUTO_INCREMENT, `event_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, @@ -294,7 +294,7 @@ CREATE TABLE IF NOT EXISTS `fuzzy_correlate_ssdeep` ( -- Table structure for `galaxies` -- -CREATE TABLE IF NOT EXISTS galaxies ( +CREATE TABLE IF NOT EXISTS `galaxies` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uuid` varchar(255) COLLATE utf8_bin NOT NULL, `name` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', @@ -317,7 +317,7 @@ CREATE TABLE IF NOT EXISTS galaxies ( -- -CREATE TABLE IF NOT EXISTS galaxy_clusters ( +CREATE TABLE IF NOT EXISTS `galaxy_clusters` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uuid` varchar(255) COLLATE utf8_bin NOT NULL, `type` varchar(255) COLLATE utf8_bin NOT NULL, @@ -343,7 +343,7 @@ CREATE TABLE IF NOT EXISTS galaxy_clusters ( -- Table structure for `galaxy_elements` -- -CREATE TABLE IF NOT EXISTS galaxy_elements ( +CREATE TABLE IF NOT EXISTS `galaxy_elements` ( `id` int(11) NOT NULL AUTO_INCREMENT, `galaxy_cluster_id` int(11) NOT NULL, `key` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '', @@ -360,7 +360,7 @@ CREATE TABLE IF NOT EXISTS galaxy_elements ( -- Table structure for `galaxy_reference` -- -CREATE TABLE IF NOT EXISTS galaxy_reference ( +CREATE TABLE IF NOT EXISTS `galaxy_reference` ( `id` int(11) NOT NULL AUTO_INCREMENT, `galaxy_cluster_id` int(11) NOT NULL, `referenced_galaxy_cluster_id` int(11) NOT NULL, @@ -488,7 +488,7 @@ CREATE TABLE IF NOT EXISTS `org_blacklists` ( -- Table structure for table `objects` -- -CREATE TABLE IF NOT EXISTS objects ( +CREATE TABLE IF NOT EXISTS `objects` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, `meta-category` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, @@ -520,7 +520,7 @@ CREATE TABLE IF NOT EXISTS objects ( -- Table structure for table `object_object_references` -- -CREATE TABLE IF NOT EXISTS object_references ( +CREATE TABLE IF NOT EXISTS `object_references` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, `timestamp` int(11) NOT NULL DEFAULT 0, @@ -548,7 +548,7 @@ CREATE TABLE IF NOT EXISTS object_references ( -- Table structure for table `object_relationships` -- -CREATE TABLE IF NOT EXISTS object_relationships ( +CREATE TABLE IF NOT EXISTS `object_relationships` ( `id` int(11) NOT NULL AUTO_INCREMENT, `version` int(11) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, @@ -564,7 +564,7 @@ CREATE TABLE IF NOT EXISTS object_relationships ( -- Table structure for table `object_templates` -- -CREATE TABLE IF NOT EXISTS object_templates ( +CREATE TABLE IF NOT EXISTS `object_templates` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `org_id` int(11) NOT NULL, @@ -590,7 +590,7 @@ CREATE TABLE IF NOT EXISTS object_templates ( -- Table structure for table `object_template_elements` -- -CREATE TABLE IF NOT EXISTS object_template_elements ( +CREATE TABLE IF NOT EXISTS `object_template_elements` ( `id` int(11) NOT NULL AUTO_INCREMENT, `object_template_id` int(11) NOT NULL, `object_relation` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin, @@ -876,7 +876,7 @@ CREATE TABLE `sharing_groups` ( -- Table structure for table sightings -- -CREATE TABLE IF NOT EXISTS sightings ( +CREATE TABLE IF NOT EXISTS `sightings` ( `id` int(11) NOT NULL AUTO_INCREMENT, `attribute_id` int(11) NOT NULL, `event_id` int(11) NOT NULL, diff --git a/app/Config/config.default.php b/app/Config/config.default.php index 7c2370c36..0f137ffea 100644 --- a/app/Config/config.default.php +++ b/app/Config/config.default.php @@ -123,6 +123,15 @@ $config = array( 'DefaultOrg' => 'DEFAULT_ORG', ), */ + /* + 'LinOTPAuth' => // Configuration for the LinOTP authentication + array( + 'baseUrl' => 'https://linotp', // The base URL of LinOTP + 'realm' => 'lino', // the (default) realm of all the users logging in through this system + 'userModel' => 'User', // name of the User class (MISP class) to check if the user exists + 'userModelKey' => 'email', // User field that will be used for querying. + ), + */ // Warning: The following is a 3rd party contribution and still untested (including security) by the MISP-project team. // Feel free to enable it and report back to us if you run into any issues. // diff --git a/app/Plugin/LinOTPAuth/Controller/Component/Auth/LinOTPAuthenticate.php b/app/Plugin/LinOTPAuth/Controller/Component/Auth/LinOTPAuthenticate.php new file mode 100644 index 000000000..bdb4ad162 --- /dev/null +++ b/app/Plugin/LinOTPAuth/Controller/Component/Auth/LinOTPAuthenticate.php @@ -0,0 +1,76 @@ +getUser($request); + return $user; + } + + /* + * Retrieve a user by validating the request data + */ + public function getUser(CakeRequest $request) + { + if (!array_key_exists("User", $request->data)) { + return false; + } + + $userFields = $request->data['User']; + $email = $userFields['email']; + $password = $userFields['password']; + CakeLog::debug("getUser email: ${email}"); + + $linotp = new LinOTP( + Configure::read("LinOTPAuth.baseUrl"), + Configure::read("LinOTPAuth.realm") + ); + + $response = $linotp->validate_check($email, $password); + + // If LinOTP didn't reject the request we can go on to further authentication steps, user login or creation + if ($response !== false) { + if ($response['value'] === true) { // user can be logged in, authentication successful + $this->settings['fields'] = array('username' => "email"); + + $user = $this->_findUser($email); + if ($user) { + // When the user logs in for the first time a password prompt will appear + // To avoid that very prompt we are changing the `change_pw` value to '0'. + if ($user['change_pw'] === "1") { + $userModel = ClassRegistry::init($this->settings['userModel']); + $user['change_pw'] = '0'; + $userModel->set(array( + "id" => $user['id'], + "change_pw" => $user['change_pw'], + )); + $userModel->save(array('User' => $user), false); + + $user = $this->_findUser($email); + } + + return $user; + } else { + CakeLog::error("User ${email} authenticated but not found in database."); + return false; + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/app/Plugin/LinOTPAuth/Lib/LinOTP.php b/app/Plugin/LinOTPAuth/Lib/LinOTP.php new file mode 100644 index 000000000..189afe832 --- /dev/null +++ b/app/Plugin/LinOTPAuth/Lib/LinOTP.php @@ -0,0 +1,169 @@ +base_url = $this->_normalize_url($base_url); + $this->realm = $realm; + $this->request_timeout = $request_timeout; + $this->ca_path = $ca_path; + } + + /** + * Strip trailing slashes (from URLs) + * @param $url + * @return bool|stringS + */ + protected function _normalize_url($url) { + return rtrim($url, "/"); + } + + /** + * Validate Check + * Performa a /validate/check call against the given LinOTP instance. + * @param user the username (opt. including the realm) + * @param password the password or OTPPin to validate + * @return bool|mixed returns true or false if the validation was successful, if more information are required (e.g. an OTP) an array is return that contains details. + */ + public function validate_check($user, $password) { + CakeLog::debug("Calling /validate/check for ${user}"); + $data = array( + "user" => $user, + "pass" => $password, + ); + + if ($this->realm != null) { + $data['realm'] = $this->realm; + } + + $response = $this->_post("/validate/check", $data); + + if ($response == false) { + CakeLog::error("LinOTP request for user ${user} failed."); + return false; + } else { + if (gettype($response) !== "object") { + CakeLog::error("Response from LinOTP is not an JSON dictionary/array. Got an " .gettype($response). ": ".$response); + return false; + } + + if (!property_exists($response,"result")) { + CakeLog::error("Missing 'result' key in LinOTP response."); + return false; + } + $result = $response->result; + + if (!property_exists($result,"status")) { + CakeLog::error("Missing 'status' key in result envelope from LinOTP."); + return false; + } + $status = $result->status; + + if (!property_exists($result, "value")) { + CakeLog::error("Missing 'value' key in result envelop from LinOTP."); + return false; + } + $value = $result->value; + + $ret = array( + "status" => $status, + "value" => $value, + ); + + if (property_exists($result, 'detail')) { + $ret['detail'] = $result->detail; + } + + return $ret; + } + } + + /** + * Perform a POST request to the given path on the configured LinOTP instance. + * @param $path string path part of the request URL + * @param $data array the post data + * @return bool|mixed false if the request failed otherwise the request body or decoded json may be returned. + */ + protected function _post($path, $data) { + $ch = curl_init(); + + $url = $this->base_url . $path; + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->request_timeout); + curl_setopt($ch, CURLOPT_USERAGENT, 'MISP LinOTPAuth'); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + // if there is a ca_path set tell curl about it. + if ($this->ca_path != null) { + curl_setopt($ch, CURLOPT_CAPATH, $this->ca_path); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); + } + + CakeLog::debug( "Sending POST request to ${url}"); + $response = curl_exec($ch); + + $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); + $content_length = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD); + $status_code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + + CakeLog::debug("Response status: ${status_code}"); + + if ($status_code >= 300 || $status_code < 200) { + CakeLog::debug("Status Code out of range: ${status_code}"); + } + + $curl_errno = curl_errno($ch); + $curl_error = curl_error($ch); + curl_close($ch); + + // if the request failed return false + if ($curl_errno !== 0) { + CakeLog::error("curl error: ${curl_error}"); + return false; + } else { + // if the response content type hints towards JSON try to deserialize it + if ($content_length > 0 && $content_type === 'application/json') { + $json_data = json_decode($response); + return $json_data; + } else { + return $response; + } + } + } + +} \ No newline at end of file diff --git a/tools/misp-wipe/misp-wipe.sh b/tools/misp-wipe/misp-wipe.sh index 7b864078e..76259eebd 100755 --- a/tools/misp-wipe/misp-wipe.sh +++ b/tools/misp-wipe/misp-wipe.sh @@ -92,6 +92,9 @@ curl --header "Authorization: $AuthKey" --header "Accept: application/json" --he echo "Updating warninglists" curl --header "Authorization: $AuthKey" --header "Accept: application/json" --header "Content-Type: application/json" -o /dev/null -s -X POST ${baseurl}/warninglists/update +echo "Updating noticelists" +curl --header "Authorization: $AuthKey" --header "Accept: application/json" --header "Content-Type: application/json" -o /dev/null -s -X POST ${baseurl}/noticelists/update + echo "Updating galaxies" curl --header "Authorization: $AuthKey" --header "Accept: application/json" --header "Content-Type: application/json" -o /dev/null -s -X POST ${baseurl}/galaxies/update diff --git a/tools/misp-wipe/misp-wipe.sql b/tools/misp-wipe/misp-wipe.sql index 5bc850aea..6fdfb9af8 100644 --- a/tools/misp-wipe/misp-wipe.sql +++ b/tools/misp-wipe/misp-wipe.sql @@ -30,6 +30,9 @@ TRUNCATE `bruteforces`; TRUNCATE `news`; TRUNCATE `template_tags`; TRUNCATE `whitelist`; +TRUNCATE `event_locks`; +TRUNCATE `fuzzy_correlate_ssdeep`; +TRUNCATE `tasks`; -- Clear tables that can be re-populated TRUNCATE `taxonomies`; @@ -42,6 +45,8 @@ TRUNCATE `galaxies`; TRUNCATE `galaxy_clusters`; TRUNCATE `galaxy_elements`; TRUNCATE `galaxy_reference`; +TRUNCATE `noticelists`; +TRUNCATE `noticelist_entries`; -- Clear tables that have defaults TRUNCATE `feeds`;