2013-03-06 11:34:22 +01:00
< ? php
App :: uses ( 'AppModel' , 'Model' );
App :: uses ( 'AuthComponent' , 'Controller/Component' );
2016-08-25 20:19:16 +02:00
App :: uses ( 'RandomTool' , 'Tools' );
2020-01-26 19:28:04 +01:00
App :: uses ( 'GpgTool' , 'Tools' );
2013-03-06 11:34:22 +01:00
2019-09-23 19:07:24 +02:00
/**
* @ property Log $Log
*/
2018-07-19 11:48:22 +02:00
class User extends AppModel
{
public $displayField = 'email' ;
2013-03-06 11:34:22 +01:00
2018-08-03 17:26:06 +02:00
public $orgField = array ( 'Organisation' , 'name' );
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
public $validate = array (
'role_id' => array (
'numeric' => array (
'rule' => array ( 'numeric' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'password' => array (
'minlength' => array (
'rule' => array ( 'passwordLength' ),
'message' => 'Password length requirement not met.' ,
//'allowEmpty' => false,
'required' => true ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
'complexity' => array (
'rule' => array ( 'complexPassword' ),
'message' => 'Password complexity requirement not met.' ,
//'allowEmpty' => false,
//'required' => true,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
'identical' => array (
'rule' => array ( 'identicalFieldValues' , 'confirm_password' ),
'message' => 'Please re-enter your password twice so that the values match.' ,
//'allowEmpty' => false,
//'required' => true,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
'org_id' => array (
'valueNotEmpty' => array (
'rule' => array ( 'valueNotEmpty' ),
),
'numeric' => array (
'rule' => array ( 'numeric' ),
'message' => 'The organisation ID has to be a numeric value.' ,
),
),
'email' => array (
'email' => array (
'rule' => array ( 'email' ),
'message' => 'Please enter a valid email address.' ,
//'allowEmpty' => false,
'required' => true ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
'unique' => array (
'rule' => 'isUnique' ,
'message' => 'An account with this email address already exists.'
),
),
'autoalert' => array (
'boolean' => array (
'rule' => array ( 'boolean' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
'required' => false ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'contactalert' => array (
'boolean' => array (
'rule' => array ( 'boolean' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
'required' => false ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'authkey' => array (
'minlength' => array (
'rule' => array ( 'minlength' , 40 ),
'message' => 'A authkey of a minimum length of 40 is required.' ,
'required' => true ,
),
'valueNotEmpty' => array (
'rule' => array ( 'valueNotEmpty' ),
),
),
'invited_by' => array (
'numeric' => array (
'rule' => array ( 'numeric' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'change_pw' => array (
'boolean' => array (
'rule' => array ( 'boolean' ),
//'message' => 'Your custom message here',
'allowEmpty' => true ,
'required' => false ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'gpgkey' => array (
'gpgvalidation' => array (
'rule' => array ( 'validateGpgkey' ),
'message' => 'GnuPG key not valid, please enter a valid key.' ,
),
),
'certif_public' => array (
'notempty' => array (
'rule' => array ( 'validateCertificate' ),
'message' => 'Certificate not valid, please enter a valid certificate (x509).' ,
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'nids_sid' => array (
'numeric' => array (
'rule' => array ( 'numeric' ),
'message' => 'A SID should be an integer.' ,
'allowEmpty' => false ,
'required' => true ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'termsaccepted' => array (
'boolean' => array (
'rule' => array ( 'boolean' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'newsread' => array (
'numeric' => array (
'rule' => array ( 'numeric' )
),
),
);
2015-10-12 09:41:20 +02:00
2018-07-19 11:48:22 +02:00
// The Associations below have been created with all possible keys, those that are not needed can be removed
public $belongsTo = array (
'Role' => array (
'className' => 'Role' ,
'foreignKey' => 'role_id' ,
'conditions' => '' ,
'fields' => '' ,
'order' => ''
),
'Organisation' => array (
'className' => 'Organisation' ,
'foreignKey' => 'org_id' ,
'conditions' => '' ,
'fields' => '' ,
'order' => ''
),
'Server' => array (
'className' => 'Server' ,
'foreignKey' => 'server_id' ,
'conditions' => '' ,
'fields' => array ( 'Server.id' , 'Server.url' , 'Server.push_rules' ),
'order' => ''
)
);
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
public $hasMany = array (
'Event' => array (
'className' => 'Event' ,
'foreignKey' => 'user_id' ,
'dependent' => false ,
'conditions' => '' ,
'fields' => '' ,
'order' => '' ,
'limit' => '' ,
'offset' => '' ,
'exclusive' => '' ,
'finderQuery' => '' ,
'counterQuery' => ''
),
2019-09-25 11:50:54 +02:00
'Post' ,
'UserSetting'
2018-07-19 11:48:22 +02:00
);
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
public $actsAs = array (
2018-08-03 17:26:06 +02:00
'SysLogLogable.SysLogLogable' => array (
2018-07-19 11:48:22 +02:00
'userModel' => 'User' ,
'userKey' => 'user_id' ,
'change' => 'full' ,
'ignore' => array ( 'password' )
),
'Trim' ,
'Containable'
);
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
public function beforeValidate ( $options = array ())
{
if ( ! isset ( $this -> data [ 'User' ][ 'id' ])) {
if (( isset ( $this -> data [ 'User' ][ 'enable_password' ]) && ( ! $this -> data [ 'User' ][ 'enable_password' ])) || ( empty ( $this -> data [ 'User' ][ 'password' ]) && empty ( $this -> data [ 'User' ][ 'confirm_password' ]))) {
$this -> data [ 'User' ][ 'password' ] = $this -> generateRandomPassword ();
$this -> data [ 'User' ][ 'confirm_password' ] = $this -> data [ 'User' ][ 'password' ];
}
}
if ( ! isset ( $this -> data [ 'User' ][ 'certif_public' ]) || empty ( $this -> data [ 'User' ][ 'certif_public' ])) {
$this -> data [ 'User' ][ 'certif_public' ] = '' ;
}
if ( ! isset ( $this -> data [ 'User' ][ 'authkey' ]) || empty ( $this -> data [ 'User' ][ 'authkey' ])) {
$this -> data [ 'User' ][ 'authkey' ] = $this -> generateAuthKey ();
}
if ( ! isset ( $this -> data [ 'User' ][ 'nids_sid' ]) || empty ( $this -> data [ 'User' ][ 'nids_sid' ])) {
$this -> data [ 'User' ][ 'nids_sid' ] = mt_rand ( 1000000 , 9999999 );
}
if ( isset ( $this -> data [ 'User' ][ 'newsread' ]) && $this -> data [ 'User' ][ 'newsread' ] === null ) {
$this -> data [ 'User' ][ 'newsread' ] = 0 ;
}
return true ;
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
public function beforeSave ( $options = array ())
{
$this -> data [ $this -> alias ][ 'date_modified' ] = time ();
if ( isset ( $this -> data [ $this -> alias ][ 'password' ])) {
$passwordHasher = new BlowfishPasswordHasher ();
$this -> data [ $this -> alias ][ 'password' ] = $passwordHasher -> hash ( $this -> data [ $this -> alias ][ 'password' ]);
}
return true ;
}
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
public function afterSave ( $created , $options = array ())
{
2019-03-05 12:24:56 +01:00
$pubToZmq = Configure :: read ( 'Plugin.ZeroMQ_enable' ) && Configure :: read ( 'Plugin.ZeroMQ_user_notifications_enable' );
$kafkaTopic = Configure :: read ( 'Plugin.Kafka_user_notifications_topic' );
$pubToKafka = Configure :: read ( 'Plugin.Kafka_enable' ) && Configure :: read ( 'Plugin.Kafka_user_notifications_enable' ) && ! empty ( $kafkaTopic );
if ( $pubToZmq || $pubToKafka ) {
2018-11-23 14:11:33 +01:00
if ( ! empty ( $this -> data )) {
$user = $this -> data ;
if ( ! isset ( $user [ 'User' ])) {
$user [ 'User' ] = $user ;
}
$action = $created ? 'edit' : 'add' ;
if ( isset ( $user [ 'User' ][ 'action' ])) {
$action = $user [ 'User' ][ 'action' ];
}
if ( isset ( $user [ 'User' ][ 'id' ])) {
$user = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array ( 'User.id' => $user [ 'User' ][ 'id' ]),
'fields' => array ( 'id' , 'email' , 'last_login' , 'org_id' , 'termsaccepted' , 'autoalert' , 'newsread' , 'disabled' ),
'contain' => array (
'Organisation' => array (
'fields' => array ( 'Organisation.id' , 'Organisation.name' , 'Organisation.description' , 'Organisation.uuid' , 'Organisation.nationality' , 'Organisation.sector' , 'Organisation.type' , 'Organisation.local' )
)
)
));
}
if ( isset ( $user [ 'User' ][ 'password' ])) {
unset ( $user [ 'User' ][ 'password' ]);
unset ( $user [ 'User' ][ 'confirm_password' ]);
}
2019-03-05 12:24:56 +01:00
if ( $pubToZmq ) {
$pubSubTool = $this -> getPubSubTool ();
$pubSubTool -> modified ( $user , 'user' , $action );
}
if ( $pubToKafka ) {
$kafkaPubTool = $this -> getKafkaPubTool ();
$kafkaPubTool -> publishJson ( $kafkaTopic , $user , $action );
}
2018-11-23 14:11:33 +01:00
}
2018-07-19 11:48:22 +02:00
}
return true ;
}
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
// Checks if the GnuPG key is a valid key, but also import it in the keychain.
2018-08-03 17:26:06 +02:00
// this will NOT fail on keys that can only be used for signing but not encryption!
2018-07-19 11:48:22 +02:00
// the method in verifyUsers will fail in that case.
public function validateGpgkey ( $check )
{
// LATER first remove the old gpgkey from the keychain
// empty value
if ( empty ( $check [ 'gpgkey' ])) {
return true ;
}
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
// we have a clean, hopefully public, key here
try {
2019-09-06 21:31:16 +02:00
$gpg = $this -> initializeGpg ();
2018-07-19 11:48:22 +02:00
try {
$keyImportOutput = $gpg -> importKey ( $check [ 'gpgkey' ]);
if ( ! empty ( $keyImportOutput [ 'fingerprint' ])) {
return true ;
}
} catch ( Exception $e ) {
$this -> log ( $e -> getMessage ());
return false ;
}
} catch ( Exception $e ) {
$this -> log ( $e -> getMessage ());
2018-08-03 17:26:06 +02:00
return true ;
2018-07-19 11:48:22 +02:00
}
}
2016-05-20 19:00:03 +02:00
2018-07-19 11:48:22 +02:00
// Checks if the certificate is a valid x509 certificate, but also import it in the keychain.
2018-08-03 17:26:06 +02:00
// this will NOT fail on keys that can only be used for signing but not encryption!
2018-07-19 11:48:22 +02:00
// the method in verifyUsers will fail in that case.
public function validateCertificate ( $check )
{
// LATER first remove the old certif_public from the keychain
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
// empty value
if ( empty ( $check [ 'certif_public' ])) {
return true ;
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
// certif_public is entered
2016-04-04 19:23:05 +02:00
2018-07-19 11:48:22 +02:00
// Check if $check is a x509 certificate
if ( openssl_x509_read ( $check [ 'certif_public' ])) {
2018-08-03 17:23:06 +02:00
return $this -> testSmimeCertificate ( $check [ 'certif_public' ]);
2018-07-19 11:48:22 +02:00
} else {
return false ;
}
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
public function passwordLength ( $check )
{
$length = Configure :: read ( 'Security.password_policy_length' );
if ( empty ( $length ) || $length < 0 ) {
$length = 12 ;
}
$value = array_values ( $check );
$value = $value [ 0 ];
if ( strlen ( $value ) < $length ) {
return false ;
}
return true ;
}
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
/*
default password :
6 characters minimum
1 or more upper - case letters
1 or more lower - case letters
1 or more digits or special characters
example : " EasyPeasy34 "
If Security . password_policy_complexity is set and valid , use the regex provided .
*/
public function complexPassword ( $check )
{
$regex = Configure :: read ( 'Security.password_policy_complexity' );
if ( empty ( $regex ) || @ preg_match ( $regex , 'test' ) === false ) {
$regex = '/^((?=.*\d)|(?=.*\W+))(?![\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/' ;
}
$value = array_values ( $check );
$value = $value [ 0 ];
return preg_match ( $regex , $value );
}
2013-03-06 11:34:22 +01:00
2019-09-06 21:31:16 +02:00
public function identicalFieldValues ( $field = array (), $compareField = null )
2018-07-19 11:48:22 +02:00
{
foreach ( $field as $key => $value ) {
$v1 = $value ;
$v2 = $this -> data [ $this -> name ][ $compareField ];
if ( $v1 !== $v2 ) {
return false ;
} else {
continue ;
}
}
return true ;
}
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
public function generateAuthKey ()
{
return ( new RandomTool ()) -> random_str ( true , 40 );
}
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
/**
* Generates a cryptographically secure password
*
* @ param int $passwordLength
* @ return string
*/
public function generateRandomPassword ( $passwordLength = 40 )
{
// makes sure, the password policy isn't undermined by setting a manual passwordLength
$policyPasswordLength = Configure :: read ( 'Security.password_policy_length' ) ? Configure :: read ( 'Security.password_policy_length' ) : false ;
if ( is_int ( $policyPasswordLength ) && $policyPasswordLength > $passwordLength ) {
$passwordLength = $policyPasswordLength ;
}
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-+=!@#$%^&*()<>/?' ;
return ( new RandomTool ()) -> random_str ( true , $passwordLength , $characters );
}
2013-07-11 14:26:28 +02:00
2013-03-06 11:34:22 +01:00
2018-07-19 11:48:22 +02:00
public function checkAndCorrectPgps ()
{
$fails = array ();
$users = $this -> find ( 'all' , array ( 'recursive' => 0 ));
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
foreach ( $users as $user ) {
if ( strlen ( $user [ 'User' ][ 'gpgkey' ]) && strpos ( $user [ 'User' ][ 'gpgkey' ], " \n " )) {
$fails [] = $user [ 'User' ][ 'id' ] . ':' . $user [ 'User' ][ 'id' ];
}
}
return $fails ;
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
public function getOrgs ()
{
$orgs = $this -> Organisation -> find ( 'list' , array (
'recursive' => - 1 ,
'fields' => array ( 'name' ),
));
return $orgs ;
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
public function getOrgMemberCount ( $org )
{
return $this -> find ( 'count' , array (
'conditions' => array (
'org =' => $org ,
)));
}
2017-02-22 10:55:24 +01:00
2018-07-19 11:48:22 +02:00
public function verifySingleGPG ( $user , $gpg = false )
{
if ( ! $gpg ) {
try {
2019-09-06 21:31:16 +02:00
$gpg = $this -> initializeGpg ();
2018-07-19 11:48:22 +02:00
} catch ( Exception $e ) {
2019-09-06 21:31:16 +02:00
$result [ 2 ] = 'GnuPG is not configured on this system.' ;
2018-07-19 11:48:22 +02:00
$result [ 0 ] = true ;
return $result ;
}
}
$result = array ();
try {
$currentTimestamp = time ();
$temp = $gpg -> importKey ( $user [ 'User' ][ 'gpgkey' ]);
$key = $gpg -> getKeys ( $temp [ 'fingerprint' ]);
2018-08-06 17:05:35 +02:00
$result [ 5 ] = $temp [ 'fingerprint' ];
2018-07-19 11:48:22 +02:00
$subKeys = $key [ 0 ] -> getSubKeys ();
$sortedKeys = array ( 'valid' => 0 , 'expired' => 0 , 'noEncrypt' => 0 );
foreach ( $subKeys as $subKey ) {
$expiration = $subKey -> getExpirationDate ();
if ( $expiration != 0 && $currentTimestamp > $expiration ) {
$sortedKeys [ 'expired' ] ++ ;
continue ;
}
if ( ! $subKey -> canEncrypt ()) {
$sortedKeys [ 'noEncrypt' ] ++ ;
continue ;
}
$sortedKeys [ 'valid' ] ++ ;
}
if ( ! $sortedKeys [ 'valid' ]) {
$result [ 2 ] = 'The user\'s GnuPG key does not include a valid subkey that could be used for encryption.' ;
if ( $sortedKeys [ 'expired' ]) {
$result [ 2 ] .= ' Found ' . $sortedKeys [ 'expired' ] . ' subkey(s) that have expired.' ;
}
if ( $sortedKeys [ 'noEncrypt' ]) {
$result [ 2 ] .= ' Found ' . $sortedKeys [ 'noEncrypt' ] . ' subkey(s) that are sign only.' ;
}
$result [ 0 ] = true ;
}
} catch ( Exception $e ) {
$result [ 2 ] = $e -> getMessage ();
$result [ 0 ] = true ;
}
$result [ 1 ] = $user [ 'User' ][ 'email' ];
$result [ 4 ] = $temp [ 'fingerprint' ];
return $result ;
}
2017-02-22 10:55:24 +01:00
2018-07-19 11:48:22 +02:00
public function verifyGPG ( $id = false )
{
$this -> Behaviors -> detach ( 'Trim' );
$results = array ();
$conditions = array ( 'not' => array ( 'gpgkey' => '' ));
if ( $id !== false ) {
$conditions [ 'User.id' ] = $id ;
}
$users = $this -> find ( 'all' , array (
'conditions' => $conditions ,
'recursive' => - 1 ,
));
if ( empty ( $users )) {
return $results ;
}
2019-09-06 21:31:16 +02:00
$gpg = $this -> initializeGpg ();
2018-07-19 11:48:22 +02:00
foreach ( $users as $k => $user ) {
$results [ $user [ 'User' ][ 'id' ]] = $this -> verifySingleGPG ( $user , $gpg );
}
return $results ;
}
2016-04-04 19:23:05 +02:00
2018-08-20 10:50:09 +02:00
private function testSmimeCertificate ( $certif_public )
{
2018-08-03 17:23:06 +02:00
$result = array ();
try {
App :: uses ( 'Folder' , 'Utility' );
App :: uses ( 'FileAccessTool' , 'Tools' );
$fileAccessTool = new FileAccessTool ();
$dir = APP . 'tmp' . DS . 'SMIME' ;
if ( ! file_exists ( $dir )) {
if ( ! mkdir ( $dir , 0750 , true )) {
throw new MethodNotAllowedException ( 'The SMIME temp directory is not writeable (app/tmp/SMIME).' );
}
}
$tempFile = $fileAccessTool -> createTempFile ( $dir , 'SMIME' );
$msg_test = $fileAccessTool -> writeToFile ( $tempFile , 'test' );
$msg_test_encrypted = $fileAccessTool -> createTempFile ( $dir , 'SMIME' );
// encrypt it
if ( openssl_pkcs7_encrypt ( $msg_test , $msg_test_encrypted , $certif_public , null , 0 , OPENSSL_CIPHER_AES_256_CBC )) {
$parse = openssl_x509_parse ( $certif_public );
// Valid certificate ?
$now = new DateTime ( " now " );
$validTo_time_t_epoch = $parse [ 'validTo_time_t' ];
$validTo_time_t = new DateTime ( " @ $validTo_time_t_epoch " );
if ( $validTo_time_t > $now ) {
// purposes smimeencrypt ?
if (( $parse [ 'purposes' ][ 5 ][ 0 ] == 1 ) && ( $parse [ 'purposes' ][ 5 ][ 2 ] == 'smimeencrypt' )) {
$result = true ;
} else {
// openssl_pkcs7_encrypt good -- Model/User purposes is NOT GOOD'
$result = 'This certificate cannot be used to encrypt email' ;
}
} else {
// openssl_pkcs7_encrypt good -- Model/User expired;
$result = 'This certificate is expired' ;
}
} else {
// openssl_pkcs7_encrypt NOT good -- Model/User
$result = 'This certificate cannot be used to encrypt email' ;
}
} catch ( Exception $e ) {
$this -> log ( $e -> getMessage ());
}
unlink ( $msg_test );
unlink ( $msg_test_encrypted );
return $result ;
}
2018-07-19 11:48:22 +02:00
public function verifyCertificate ()
{
$this -> Behaviors -> detach ( 'Trim' );
$results = array ();
$users = $this -> find ( 'all' , array (
'conditions' => array ( 'not' => array ( 'certif_public' => '' )),
'recursive' => - 1 ,
));
foreach ( $users as $k => $user ) {
2018-08-03 17:23:06 +02:00
$result = $this -> testSmimeCertificate ( $user [ 'User' ][ 'certif_public' ]);
if ( $result !== true ) {
$results [ $user [ 'User' ][ 'id' ]] = array ( 0 => true , 1 => $user [ 'User' ][ 'email' ]);
2018-07-19 11:48:22 +02:00
}
}
return $results ;
}
2016-04-04 19:23:05 +02:00
2018-07-19 11:48:22 +02:00
public function getPGP ( $id )
{
$result = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'fields' => array ( 'id' , 'gpgkey' ),
'conditions' => array ( 'id' => $id ),
));
return $result [ 'User' ][ 'gpgkey' ];
}
2016-04-04 19:23:05 +02:00
2018-07-19 11:48:22 +02:00
public function getCertificate ( $id )
{
$result = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'fields' => array ( 'id' , 'certif_public' ),
'conditions' => array ( 'id' => $id ),
));
return $result [ 'User' ][ 'certif_public' ];
}
2016-05-31 17:36:06 +02:00
2018-07-19 11:48:22 +02:00
// get the current user and rearrange it to be in the same format as in the auth component
public function getAuthUser ( $id )
{
if ( empty ( $id )) {
2019-09-25 11:50:54 +02:00
throw new NotFoundException ( 'Invalid user ID.' );
2018-07-19 11:48:22 +02:00
}
$conditions = array ( 'User.id' => $id );
2019-10-15 07:24:43 +02:00
$user = $this -> find (
'first' ,
array (
'conditions' => $conditions ,
'recursive' => - 1 ,
'contain' => array (
'Organisation' ,
'Role' ,
'Server' ,
'UserSetting'
)
)
);
2018-07-19 11:48:22 +02:00
if ( empty ( $user )) {
return $user ;
}
// Rearrange it a bit to match the Auth object created during the login
$user [ 'User' ][ 'Role' ] = $user [ 'Role' ];
$user [ 'User' ][ 'Organisation' ] = $user [ 'Organisation' ];
$user [ 'User' ][ 'Server' ] = $user [ 'Server' ];
2019-10-15 07:24:43 +02:00
$user [ 'User' ][ 'UserSetting' ] = $user [ 'UserSetting' ];
2018-07-19 11:48:22 +02:00
unset ( $user [ 'Organisation' ], $user [ 'Role' ], $user [ 'Server' ]);
return $user [ 'User' ];
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
// get the current user and rearrange it to be in the same format as in the auth component
public function getAuthUserByAuthkey ( $id )
{
$conditions = array ( 'User.authkey' => $id );
$user = $this -> find ( 'first' , array ( 'conditions' => $conditions , 'recursive' => - 1 , 'contain' => array ( 'Organisation' , 'Role' , 'Server' )));
if ( empty ( $user )) {
return $user ;
}
// Rearrange it a bit to match the Auth object created during the login
$user [ 'User' ][ 'Role' ] = $user [ 'Role' ];
$user [ 'User' ][ 'Organisation' ] = $user [ 'Organisation' ];
$user [ 'User' ][ 'Server' ] = $user [ 'Server' ];
return $user [ 'User' ];
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
public function getAuthUserByExternalAuth ( $auth_key )
{
$conditions = array (
'User.external_auth_key' => $auth_key ,
'User.external_auth_required' => true
);
$user = $this -> find ( 'first' , array (
'conditions' => $conditions ,
'recursive' => - 1 ,
'contain' => array (
'Organisation' ,
'Role' ,
'Server'
)
));
if ( empty ( $user )) {
return $user ;
}
// Rearrange it a bit to match the Auth object created during the login
$user [ 'User' ][ 'Role' ] = $user [ 'Role' ];
$user [ 'User' ][ 'Organisation' ] = $user [ 'Organisation' ];
$user [ 'User' ][ 'Server' ] = $user [ 'Server' ];
unset ( $user [ 'Organisation' ], $user [ 'Role' ], $user [ 'Server' ]);
return $user [ 'User' ];
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
// Fetch all users that have access to an event / discussion for e-mailing (or maybe something else in the future.
// parameters are an array of org IDs that are owners (for an event this would be orgc and org)
public function getUsersWithAccess ( $owners = array (), $distribution , $sharing_group_id = 0 , $userConditions = array ())
{
$sgModel = ClassRegistry :: init ( 'SharingGroup' );
$conditions = array ();
$validOrgs = array ();
$all = true ;
2015-04-20 11:46:55 +02:00
2018-07-19 11:48:22 +02:00
// add owners to the conditions
if ( $distribution == 0 || $distribution == 4 ) {
$all = false ;
$validOrgs = $owners ;
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
// add all orgs to the conditions that can see the SG
if ( $distribution == 4 ) {
$sgOrgs = $sgModel -> getOrgsWithAccess ( $sharing_group_id );
if ( $sgOrgs === true ) {
$all = true ;
} else {
$validOrgs = array_merge ( $validOrgs , $sgOrgs );
}
}
$validOrgs = array_unique ( $validOrgs );
$conditions [ 'AND' ][] = array ( 'disabled' => 0 );
if ( ! $all ) {
$conditions [ 'AND' ][ 'OR' ][] = array ( 'org_id' => $validOrgs );
2015-12-18 16:33:41 +01:00
2018-07-19 11:48:22 +02:00
// Add the site-admins to the list
$roles = $this -> Role -> find ( 'all' , array (
'conditions' => array ( 'perm_site_admin' => 1 ),
'fields' => array ( 'id' )
));
$roleIDs = array ();
foreach ( $roles as $role ) {
$roleIDs [] = $role [ 'Role' ][ 'id' ];
}
$conditions [ 'AND' ][ 'OR' ][] = array ( 'role_id' => $roleIDs );
}
$conditions [ 'AND' ][] = $userConditions ;
$users = $this -> find ( 'all' , array (
'conditions' => $conditions ,
'recursive' => - 1 ,
'fields' => array ( 'id' , 'email' , 'gpgkey' , 'certif_public' , 'org_id' ),
'contain' => array ( 'Role' => array ( 'fields' => array ( 'perm_site_admin' ))),
));
foreach ( $users as $k => $user ) {
$user = $user [ 'User' ];
unset ( $users [ $k ][ 'User' ]);
$users [ $k ] = array_merge ( $user , $users [ $k ]);
}
return $users ;
}
2015-06-10 18:07:48 +02:00
2019-08-29 09:24:33 +02:00
public function sendEmailExternal ( $user , $params )
{
$this -> Log = ClassRegistry :: init ( 'Log' );
$params [ 'body' ] = str_replace ( '\n' , PHP_EOL , $params [ 'body' ]);
$Email = new CakeEmail ();
$recipient = array ( 'User' => array ( 'email' => $params [ 'to' ]));
$failed = false ;
2019-09-19 15:14:51 +02:00
$mock = false ;
2019-08-29 09:24:33 +02:00
if ( ! empty ( $params [ 'gpgkey' ])) {
$recipient [ 'User' ][ 'gpgkey' ] = $params [ 'gpgkey' ];
$encryptionResult = $this -> __encryptUsingGPG ( $Email , $params [ 'body' ], $params [ 'subject' ], $recipient );
if ( isset ( $encryptionResult [ 'failed' ])) {
2019-09-19 15:14:51 +02:00
$mock = true ;
2019-08-29 09:24:33 +02:00
}
if ( isset ( $encryptionResult [ 'failureReason' ])) {
$failureReason = $encryptionResult [ 'failureReason' ];
}
}
if ( ! $failed ) {
$replyToLog = '' ;
$user = array ( 'User' => $user );
$attachments = array ();
$Email -> replyTo ( $params [ 'reply-to' ]);
if ( ! empty ( $params [ 'requestor_gpgkey' ])) {
$attachments [ 'gpgkey.asc' ] = array (
'data' => $params [ 'requestor_gpgkey' ]
);
}
$Email -> from ( Configure :: read ( 'MISP.email' ));
$Email -> returnPath ( Configure :: read ( 'MISP.email' ));
$Email -> to ( $params [ 'to' ]);
$Email -> subject ( $params [ 'subject' ]);
$Email -> emailFormat ( 'text' );
if ( ! empty ( $params [ 'attachments' ])) {
foreach ( $params [ 'attachments' ] as $key => $value ) {
$attachments [ $k ] = array ( 'data' => $value );
}
}
$Email -> attachments ( $attachments );
2019-09-19 15:14:51 +02:00
if ( ! empty ( Configure :: read ( 'MISP.disable_emailing' )) || ! empty ( $params [ 'mock' ])) {
2019-08-30 11:10:59 +02:00
$Email -> transport ( 'Debug' );
2019-08-30 11:19:43 +02:00
$mock = true ;
2019-08-30 11:10:59 +02:00
}
2019-08-29 09:24:33 +02:00
$result = $Email -> send ( $params [ 'body' ]);
$Email -> reset ();
2019-08-30 11:19:43 +02:00
if ( $result && ! $mock ) {
return true ;
}
2019-08-29 09:24:33 +02:00
return $result ;
}
return false ;
}
2018-07-19 11:48:22 +02:00
// all e-mail sending is now handled by this method
// Just pass the user ID in an array that is the target of the e-mail along with the message body and the alternate message body if the message cannot be encrypted
// the remaining two parameters are the e-mail subject and a secondary user object which will be used as the replyto address if set. If it is set and an encryption key for the replyTo user exists, then his/her public key will also be attached
public function sendEmail ( $user , $body , $bodyNoEnc = false , $subject , $replyToUser = false )
{
$this -> Log = ClassRegistry :: init ( 'Log' );
if ( Configure :: read ( 'MISP.disable_emailing' )) {
$this -> Log -> create ();
$this -> Log -> save ( array (
'org' => 'SYSTEM' ,
'model' => 'User' ,
'model_id' => $user [ 'User' ][ 'id' ],
'email' => $user [ 'User' ][ 'email' ],
'action' => 'email' ,
'title' => 'Email to ' . $user [ 'User' ][ 'email' ] . ', titled "' . $subject . '" failed. Reason: Emailing is currently disabled on this instance.' ,
'change' => null ,
));
return true ;
}
2018-08-03 18:26:45 +02:00
if ( ! empty ( $user [ 'User' ][ 'disabled' ])) {
2018-07-19 11:48:22 +02:00
return true ;
}
$failed = false ;
$failureReason = " " ;
// check if the e-mail can be encrypted
2018-08-03 18:26:45 +02:00
$canEncryptGPG = isset ( $user [ 'User' ][ 'gpgkey' ]) && ! empty ( $user [ 'User' ][ 'gpgkey' ]);
$canEncryptSMIME = isset ( $user [ 'User' ][ 'certif_public' ]) && ! empty ( $user [ 'User' ][ 'certif_public' ]) && Configure :: read ( 'SMIME.enabled' );
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
// If bodyonlyencrypted is enabled and the user has no encryption key, use the alternate body (if it exists)
if ( Configure :: read ( 'GnuPG.bodyonlyencrypted' ) && ! $canEncryptSMIME && ! $canEncryptGPG && $bodyNoEnc ) {
$body = $bodyNoEnc ;
}
$body = str_replace ( '\n' , PHP_EOL , $body );
2016-04-04 19:23:05 +02:00
2018-07-19 11:48:22 +02:00
$Email = new CakeEmail ();
// If we cannot encrypt the mail and the server settings restricts sending unencrypted messages, return false
if ( ! $failed && Configure :: read ( 'GnuPG.onlyencrypted' ) && ! $canEncryptGPG && ! $canEncryptSMIME ) {
$failed = true ;
$failureReason = " encrypted messages are enforced and the message could not be encrypted for this user as no valid encryption key was found. " ;
}
// Let's encrypt the message if we can
if ( ! $failed && $canEncryptGPG ) {
2018-08-03 18:26:45 +02:00
$encryptionResult = $this -> __encryptUsingGPG ( $Email , $body , $subject , $user );
if ( isset ( $encryptionResult [ 'failed' ])) {
2018-07-19 11:48:22 +02:00
$failed = true ;
}
2018-08-03 18:26:45 +02:00
if ( isset ( $encryptionResult [ 'failureReason' ])) {
$failureReason = $encryptionResult [ 'failureReason' ];
}
2018-07-19 11:48:22 +02:00
}
// SMIME if not GPG key
if ( ! $failed && ! $canEncryptGPG && $canEncryptSMIME ) {
2018-08-15 08:49:16 +02:00
$encryptionResult = $this -> __encryptUsingSmime ( $Email , $body , $subject , $user );
2018-08-03 18:26:45 +02:00
if ( isset ( $encryptionResult [ 'failed' ])) {
2018-07-19 11:48:22 +02:00
$failed = true ;
}
2018-08-03 18:26:45 +02:00
if ( isset ( $encryptionResult [ 'failureReason' ])) {
$failureReason = $encryptionResult [ 'failureReason' ];
}
2018-07-19 11:48:22 +02:00
}
$replyToLog = '' ;
if ( ! $failed ) {
2018-08-05 18:14:25 +02:00
$result = $this -> __finaliseAndSendEmail ( $replyToUser , $Email , $replyToLog , $user , $subject , $body );
2018-07-19 11:48:22 +02:00
}
$this -> Log = ClassRegistry :: init ( 'Log' );
$this -> Log -> create ();
if ( ! $failed && $result ) {
$this -> Log -> save ( array (
'org' => 'SYSTEM' ,
'model' => 'User' ,
'model_id' => $user [ 'User' ][ 'id' ],
'email' => $user [ 'User' ][ 'email' ],
'action' => 'email' ,
'title' => 'Email ' . $replyToLog . ' to ' . $user [ 'User' ][ 'email' ] . ' sent, titled "' . $subject . '".' ,
'change' => null ,
));
return true ;
} else {
2018-08-03 18:26:45 +02:00
if ( empty ( $failureReason )) {
2018-07-19 11:48:22 +02:00
$failureReason = " there was an error sending the e-mail. " ;
}
$this -> Log -> save ( array (
'org' => 'SYSTEM' ,
'model' => 'User' ,
'model_id' => $user [ 'User' ][ 'id' ],
'email' => $user [ 'User' ][ 'email' ],
'action' => 'email' ,
'title' => 'Email ' . $replyToLog . ' to ' . $user [ 'User' ][ 'email' ] . ', titled "' . $subject . '" failed. Reason: ' . $failureReason ,
'change' => null ,
));
}
return false ;
}
2016-06-04 01:08:16 +02:00
2019-08-29 09:24:33 +02:00
private function __finaliseAndSendEmail ( $replyToUser , & $Email , & $replyToLog , $user , $subject , $body , $additionalAttachments = false )
2018-08-03 18:26:45 +02:00
{
// If the e-mail is sent on behalf of a user, then we want the target user to be able to respond to the sender
// For this reason we should also attach the public key of the sender along with the message (if applicable)
2019-08-29 09:24:33 +02:00
$attachments = array ();
2018-08-03 18:26:45 +02:00
if ( $replyToUser != false ) {
$Email -> replyTo ( $replyToUser [ 'User' ][ 'email' ]);
if ( ! empty ( $replyToUser [ 'User' ][ 'gpgkey' ])) {
2019-08-29 09:24:33 +02:00
$attachments [ 'gpgkey.asc' ] = array (
'data' => $replyToUser [ 'User' ][ 'gpgkey' ]
);
2018-08-03 18:26:45 +02:00
} elseif ( ! empty ( $replyToUser [ 'User' ][ 'certif_public' ])) {
2019-08-29 09:24:33 +02:00
$attachments [ $replyToUser [ 'User' ][ 'email' ] . '.pem' ] = array (
'data' => $replyToUser [ 'User' ][ 'certif_public' ]
);
2018-08-03 18:26:45 +02:00
}
$replyToLog = 'from ' . $replyToUser [ 'User' ][ 'email' ];
}
$Email -> from ( Configure :: read ( 'MISP.email' ));
$Email -> returnPath ( Configure :: read ( 'MISP.email' ));
$Email -> to ( $user [ 'User' ][ 'email' ]);
$Email -> subject ( $subject );
$Email -> emailFormat ( 'text' );
2019-08-29 09:24:33 +02:00
if ( ! empty ( $additionalAttachments )) {
foreach ( $additionalAttachments as $key => $value ) {
$attachments [ $k ] = array ( 'data' => $value );
}
}
$Email -> attachments ( $attachments );
2018-08-03 18:26:45 +02:00
$result = $Email -> send ( $body );
$Email -> reset ();
2018-08-05 18:14:25 +02:00
return $result ;
2018-08-03 18:26:45 +02:00
}
private function __encryptUsingGPG ( & $Email , & $body , $subject , $user )
{
$failed = false ;
// Sign the body
require_once 'Crypt/GPG.php' ;
try {
2019-02-10 13:08:12 +01:00
$gpg = new Crypt_GPG ( array ( 'homedir' => Configure :: read ( 'GnuPG.homedir' ), 'gpgconf' => Configure :: read ( 'GnuPG.gpgconf' ), 'binary' => ( Configure :: read ( 'GnuPG.binary' ) ? Configure :: read ( 'GnuPG.binary' ) : '/usr/bin/gpg' ), 'debug' )); // , 'debug' => true
2018-08-03 18:26:45 +02:00
if ( Configure :: read ( 'GnuPG.sign' )) {
$gpg -> addSignKey ( Configure :: read ( 'GnuPG.email' ), Configure :: read ( 'GnuPG.password' ));
$body = $gpg -> sign ( $body , Crypt_GPG :: SIGN_MODE_CLEAR );
}
} catch ( Exception $e ) {
$failureReason = " the message could not be signed. The following error message was returned by gpg: " . $e -> getMessage ();
$this -> log ( $e -> getMessage ());
$failed = true ;
}
if ( ! $failed ) {
$keyImportOutput = $gpg -> importKey ( $user [ 'User' ][ 'gpgkey' ]);
try {
$key = $gpg -> getKeys ( $keyImportOutput [ 'fingerprint' ]);
$subKeys = $key [ 0 ] -> getSubKeys ();
$canEncryptGPG = false ;
$currentTimestamp = time ();
foreach ( $subKeys as $subKey ) {
$expiration = $subKey -> getExpirationDate ();
if (( $expiration == 0 || $currentTimestamp < $expiration ) && $subKey -> canEncrypt ()) {
$canEncryptGPG = true ;
}
}
if ( $canEncryptGPG ) {
$gpg -> addEncryptKey ( $keyImportOutput [ 'fingerprint' ]); // use the key that was given in the import
$body = $gpg -> encrypt ( $body , true );
} else {
$failed = true ;
$failureReason = " the message could not be encrypted because the provided key is either expired or cannot be used for encryption. " ;
}
} catch ( Exception $e ) {
// despite the user having a GnuPG key and the signing already succeeding earlier, we get an exception. This must mean that there is an issue with the user's key.
$failureReason = " the message could not be encrypted because there was an issue with the user's GnuPG key. The following error message was returned by gpg: " . $e -> getMessage ();
$this -> log ( $e -> getMessage ());
$failed = true ;
}
}
if ( ! empty ( $failed )) {
return array ( 'failed' => $failed , 'failureReason' => $failureReason );
}
return true ;
}
2018-08-15 08:49:16 +02:00
private function __encryptUsingSmime ( & $Email , & $body , $subject , $user )
2018-08-03 18:26:45 +02:00
{
try {
$prependedBody = 'Content-Transfer-Encoding: 7bit' . PHP_EOL . 'Content-Type: text/plain;' . PHP_EOL . ' charset=us-ascii' . PHP_EOL . PHP_EOL . $body ;
App :: uses ( 'Folder' , 'Utility' );
App :: uses ( 'FileAccessTool' , 'Tools' );
$fileAccessTool = new FileAccessTool ();
$dir = APP . 'tmp' . DS . 'SMIME' ;
if ( ! file_exists ( $dir )) {
if ( ! mkdir ( $dir , 0750 , true )) {
throw new MethodNotAllowedException ( 'The SMIME temp directory is not writeable (app/tmp/SMIME).' );
}
}
// save message to file
$tempFile = $fileAccessTool -> createTempFile ( $dir , 'SMIME' );
$msg = $fileAccessTool -> writeToFile ( $tempFile , $prependedBody );
$headers_smime = array ( " To " => $user [ 'User' ][ 'email' ], " From " => Configure :: read ( 'MISP.email' ), " Subject " => $subject );
$canSign = true ;
if (
! empty ( Configure :: read ( 'SMIME.cert_public_sign' )) &&
is_readable ( Configure :: read ( 'SMIME.cert_public_sign' )) &&
! empty ( Configure :: read ( 'SMIME.key_sign' )) &&
is_readable ( Configure :: read ( 'SMIME.key_sign' ))
) {
$signed = $fileAccessTool -> createTempFile ( $dir , 'SMIME' );
if ( openssl_pkcs7_sign ( $msg , $signed , 'file://' . Configure :: read ( 'SMIME.cert_public_sign' ), array ( 'file://' . Configure :: read ( 'SMIME.key_sign' ), Configure :: read ( 'SMIME.password' )), array (), PKCS7_TEXT )) {
$bodySigned = $fileAccessTool -> readFromFile ( $signed );
unlink ( $msg );
unlink ( $signed );
} else {
unlink ( $msg );
unlink ( $signed );
throw new Exception ( 'Failed while attempting to sign the SMIME message.' );
}
// save message to file
$tempFile = $fileAccessTool -> createTempFile ( $dir , 'SMIME' );
$msg_signed = $fileAccessTool -> writeToFile ( $tempFile , $bodySigned );
} else {
$msg_signed = $msg ;
}
$msg_signed_encrypted = $fileAccessTool -> createTempFile ( $dir , 'SMIME' );
// encrypt it
if ( openssl_pkcs7_encrypt ( $msg_signed , $msg_signed_encrypted , $user [ 'User' ][ 'certif_public' ], $headers_smime , 0 , OPENSSL_CIPHER_AES_256_CBC )) {
$bodyEncSig = $fileAccessTool -> readFromFile ( $msg_signed_encrypted );
unlink ( $msg_signed );
unlink ( $msg_signed_encrypted );
$parts = explode ( " \n \n " , $bodyEncSig );
$bodyEncSig = $parts [ 1 ];
// SMIME transport (hardcoded headers
$Email = $Email -> transport ( 'Smime' );
$body = $bodyEncSig ;
} else {
unlink ( $msg_signed );
unlink ( $msg_signed_encrypted );
throw new Exception ( 'Could not encrypt the SMIME message.' );
}
} catch ( Exception $e ) {
// despite the user having a certificate. This must mean that there is an issue with the user's certificate.
$result [ 'failureReason' ] = " the message could not be encrypted because there was an issue with the user's public certificate. The following error message was returned by openssl: " . $e -> getMessage ();
$this -> log ( $e -> getMessage ());
$result [ 'failed' ] = true ;
}
return $result ;
}
2018-07-19 11:48:22 +02:00
public function adminMessageResolve ( $message )
{
$resolveVars = array ( '$contact' => 'MISP.contact' , '$org' => 'MISP.org' , '$misp' => 'MISP.baseurl' );
foreach ( $resolveVars as $k => $v ) {
$v = Configure :: read ( $v );
$message = str_replace ( $k , $v , $message );
}
return $message ;
}
2016-06-04 01:08:16 +02:00
2019-09-23 20:56:52 +02:00
/**
* @ param string $email
* @ return array
* @ throws Exception
*/
public function searchGpgKey ( $email )
2018-07-19 11:48:22 +02:00
{
2019-09-23 20:56:52 +02:00
$gpgTool = new GpgTool ();
return $gpgTool -> searchGpgKey ( $email );
2018-07-19 11:48:22 +02:00
}
2016-06-04 01:08:16 +02:00
2019-09-23 20:56:52 +02:00
/**
* @ param string $fingerprint
* @ return string | null
* @ throws Exception
*/
public function fetchGpgKey ( $fingerprint )
2018-07-19 11:48:22 +02:00
{
2019-09-23 20:56:52 +02:00
$gpgTool = new GpgTool ();
return $gpgTool -> fetchGpgKey ( $fingerprint );
2018-07-19 11:48:22 +02:00
}
2016-06-04 01:08:16 +02:00
2018-07-19 11:48:22 +02:00
public function describeAuthFields ()
{
$fields = array ();
$fields = array_merge ( $fields , array_keys ( $this -> getColumnTypes ()));
2018-08-03 17:30:59 +02:00
foreach ( $fields as $k => $field ) {
if ( in_array ( $field , array ( 'gpgkey' , 'certif_public' ))) {
unset ( $fields [ $k ]);
}
2018-07-19 11:48:22 +02:00
}
2018-08-03 17:30:59 +02:00
$fields = array_values ( $fields );
2018-07-19 11:48:22 +02:00
$relatedModels = array_keys ( $this -> belongsTo );
foreach ( $relatedModels as $relatedModel ) {
$fields [] = $relatedModel . '.*' ;
}
return $fields ;
}
2016-08-22 02:52:51 +02:00
2018-07-19 11:48:22 +02:00
public function getMembersCount ( $org_id = false )
{
// for Organizations List
$conditions = array ();
$findType = 'all' ;
if ( $org_id !== false ) {
$findType = 'first' ;
$conditions = array ( 'User.org_id' => $org_id );
}
$fields = array ( 'org_id' , 'COUNT(User.id) AS num_members' );
$params = array (
'fields' => $fields ,
'recursive' => - 1 ,
'group' => array ( 'org_id' ),
'order' => array ( 'org_id' ),
'conditions' => $conditions
);
$orgs = $this -> find ( $findType , $params );
if ( empty ( $orgs )) {
return 0 ;
}
if ( $org_id !== false ) {
return $orgs [ 0 ][ 'num_members' ];
} else {
$usersPerOrg = [];
foreach ( $orgs as $key => $value ) {
$usersPerOrg [ $value [ 'User' ][ 'org_id' ]] = $value [ 0 ][ 'num_members' ];
}
return $usersPerOrg ;
}
}
2016-08-23 16:20:39 +02:00
2018-07-19 11:48:22 +02:00
public function findAdminsResponsibleForUser ( $user )
{
$admin = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array (
'Role.perm_admin' => 1 ,
'User.disabled' => 0 ,
'User.org_id' => $user [ 'org_id' ]
),
'contain' => array (
'Role' => array ( 'fields' => array ( 'perm_admin' ))
),
'fields' => array ( 'User.id' , 'User.email' , 'User.org_id' )
));
if ( count ( $admin ) == 0 ) {
$admin = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array (
'Role.perm_site_admin' => 1 ,
'User.disabled' => 0 ,
),
'contain' => array (
'Role' => array ( 'fields' => array ( 'perm_site_admin' ))
),
'fields' => array ( 'User.id' , 'User.email' , 'User.org_id' )
));
}
2016-08-23 16:20:39 +02:00
2018-07-19 11:48:22 +02:00
return $admin [ 'User' ];
}
2017-02-02 11:11:51 +01:00
2018-07-19 11:48:22 +02:00
public function initiatePasswordReset ( $user , $firstTime = false , $simpleReturn = false , $fixedPassword = false )
{
$org = Configure :: read ( 'MISP.org' );
$options = array ( 'passwordResetText' , 'newUserText' );
$subjects = array ( '[' . $org . ' MISP] New user registration' , '[' . $org . ' MISP] Password reset' );
$textToFetch = $options [( $firstTime ? 0 : 1 )];
$subject = $subjects [( $firstTime ? 0 : 1 )];
$this -> Server = ClassRegistry :: init ( 'Server' );
$body = Configure :: read ( 'MISP.' . $textToFetch );
if ( ! $body ) {
$body = $this -> Server -> serverSettings [ 'MISP' ][ $textToFetch ][ 'value' ];
}
$body = $this -> adminMessageResolve ( $body );
if ( $fixedPassword ) {
$password = $fixedPassword ;
} else {
$password = $this -> generateRandomPassword ();
}
$body = str_replace ( '$password' , $password , $body );
$body = str_replace ( '$username' , $user [ 'User' ][ 'email' ], $body );
$result = $this -> sendEmail ( $user , $body , false , $subject );
if ( $result ) {
$this -> id = $user [ 'User' ][ 'id' ];
$this -> saveField ( 'password' , $password );
$this -> saveField ( 'change_pw' , '1' );
if ( $simpleReturn ) {
return true ;
} else {
return array ( 'body' => json_encode ( array ( 'saved' => true , 'success' => 'New credentials sent.' )), 'status' => 200 );
}
}
if ( $simpleReturn ) {
return false ;
} else {
return array ( 'body' => json_encode ( array ( 'saved' => false , 'errors' => 'There was an error notifying the user. His/her credentials were not altered.' )), 'status' => 200 );
}
}
2017-02-22 17:12:32 +01:00
2018-07-19 11:48:22 +02:00
public function getOrgAdminsForOrg ( $org_id , $excludeUserId = false )
{
$adminRoles = $this -> Role -> find ( 'list' , array (
'recursive' => - 1 ,
'conditions' => array ( 'perm_admin' => 1 ),
'fields' => array ( 'Role.id' , 'Role.id' )
));
$conditions = array (
'User.org_id' => $org_id ,
'User.disabled' => 0 ,
'User.role_id' => $adminRoles
);
if ( $excludeUserId ) {
$conditions [ 'User.id !=' ] = $excludeUserId ;
}
return $this -> find ( 'list' , array (
'recursive' => - 1 ,
'conditions' => $conditions ,
'fields' => array (
'User.id' , 'User.email'
)
));
}
2017-07-12 15:38:34 +02:00
2018-07-19 11:48:22 +02:00
public function verifyPassword ( $user_id , $password )
{
$currentUser = $this -> find ( 'first' , array (
2019-09-06 21:31:16 +02:00
'conditions' => array ( 'User.id' => $user_id ),
'recursive' => - 1 ,
'fields' => array ( 'User.password' )
2018-07-19 11:48:22 +02:00
));
if ( empty ( $currentUser )) {
return false ;
}
if ( strlen ( $currentUser [ 'User' ][ 'password' ]) == 40 ) {
App :: uses ( 'SimplePasswordHasher' , 'Controller/Component/Auth' );
$passwordHasher = new SimplePasswordHasher ();
} else {
$passwordHasher = new BlowfishPasswordHasher ();
}
$hashed = $passwordHasher -> check ( $password , $currentUser [ 'User' ][ 'password' ]);
return $hashed ;
}
2018-06-20 07:32:52 +02:00
2018-07-19 11:48:22 +02:00
public function createInitialUser ( $org_id )
{
$authKey = $this -> generateAuthKey ();
$admin = array ( 'User' => array (
'id' => 1 ,
'email' => 'admin@admin.test' ,
'org_id' => $org_id ,
'password' => 'admin' ,
'confirm_password' => 'admin' ,
'authkey' => $authKey ,
'nids_sid' => 4000000 ,
'newsread' => 0 ,
'role_id' => 1 ,
'change_pw' => 1
));
$this -> validator () -> remove ( 'password' ); // password is too simple, remove validation
$this -> save ( $admin );
return $authKey ;
}
2019-09-09 13:00:21 +02:00
public function resetAllSyncAuthKeysRouter ( $user , $jobId = false )
{
if ( Configure :: read ( 'MISP.background_jobs' )) {
$job = ClassRegistry :: init ( 'Job' );
$job -> create ();
$eventModel = ClassRegistry :: init ( 'Event' );
$data = array (
'worker' => $eventModel -> __getPrioWorkerIfPossible (),
'job_type' => __ ( 'reset_all_sync_api_keys' ),
'job_input' => __ ( 'Reseting all API keys' ),
'status' => 0 ,
'retries' => 0 ,
'org_id' => $user [ 'org_id' ],
'org' => $user [ 'Organisation' ][ 'name' ],
'message' => 'Issuing new API keys to all sync users.' ,
);
$job -> save ( $data );
$jobId = $job -> id ;
$process_id = CakeResque :: enqueue (
'prio' ,
'AdminShell' ,
array ( 'resetSyncAuthkeys' , $user [ 'id' ], $jobId ),
true
);
$job -> saveField ( 'process_id' , $process_id );
return true ;
} else {
return $this -> resetAllSyncAuthKeys ( $user );
}
}
public function resetAllSyncAuthKeys ( $user , $jobId = false )
{
$affected_users = $this -> find ( 'all' , array (
'recursive' => - 1 ,
'contain' => array ( 'Role' ),
'conditions' => array (
'OR' => array (
'Role.perm_sync' => 1 ,
'Role.perm_admin' => 1
),
'Role.perm_site_admin' => 0
)
));
$results = array ( 'success' => 0 , 'fails' => 0 );
$user_count = count ( $affected_users );
if ( $jobId ) {
$job = ClassRegistry :: init ( 'Job' );
$existingJob = $job -> find ( 'first' , array (
'conditions' => array ( 'Job.id' => $jobId ),
'recursive' => - 1
));
if ( empty ( $existingJob )) {
$jobId = false ;
}
}
foreach ( $affected_users as $k => $affected_user ) {
try {
$reset_result = $this -> resetauthkey ( $user , $affected_user [ 'User' ][ 'id' ], true );
if ( $reset_result ) {
$results [ 'success' ] += 1 ;
} else {
$results [ 'fails' ] += 1 ;
}
} catch ( Exception $e ) {
$results [ 'fails' ] += 1 ;
}
if ( $jobId ) {
if ( $k % 100 == 0 ) {
$job -> id = $jobId ;
2019-10-03 12:02:21 +02:00
$job -> saveField ( 'progress' , 100 * (( $k + 1 ) / $user_count ));
2019-09-09 13:00:21 +02:00
$job -> saveField ( 'message' , __ ( 'Reset in progress - %s/%s.' , $k , $user_count ));
}
}
}
if ( $jobId ) {
$message = __ ( '%s authkeys reset, %s could not be reset' , $results [ 'success' ], $results [ 'fails' ]);
$job -> saveField ( 'progress' , 100 );
$job -> saveField ( 'message' , $message );
$job -> saveField ( 'status' , 4 );
}
return $results ;
}
public function resetauthkey ( $user , $id , $alert = false )
{
$this -> id = $id ;
if ( ! $id || ! $this -> exists ( $id )) {
return false ;
}
$updatedUser = $this -> read ();
$oldKey = $this -> data [ 'User' ][ 'authkey' ];
if ( empty ( $user [ 'Role' ][ 'perm_site_admin' ]) && ! ( $user [ 'Role' ][ 'perm_admin' ] && $user [ 'org_id' ] == $updatedUser [ 'User' ][ 'org_id' ]) && ( $user [ 'id' ] != $id )) {
return false ;
}
$newkey = $this -> generateAuthKey ();
$this -> saveField ( 'authkey' , $newkey );
$this -> extralog (
$user ,
'reset_auth_key' ,
sprintf (
__ ( 'Authentication key for user %s (%s) updated.' ),
$updatedUser [ 'User' ][ 'id' ],
$updatedUser [ 'User' ][ 'email' ]
),
2020-01-27 16:58:46 +01:00
$fieldsResult = [ 'authkey' => [ $oldKey , $newkey ]],
2019-09-09 13:00:21 +02:00
$updatedUser
);
if ( $alert ) {
$baseurl = Configure :: read ( 'MISP.external_baseurl' );
if ( empty ( $baseurl )) {
$baseurl = Configure :: read ( 'MISP.baseurl' );
}
$body = __ (
" Dear user, \n \n an API key reset has been triggered by an administrator for your user account on %s. \n \n Your new API key is: %s \n \n Please update your server's sync setup to reflect this change. \n \n We apologise for the inconvenience. " ,
$baseurl ,
$newkey
);
$bodyNoEnc = __ (
" Dear user, \n \n an API key reset has been triggered by an administrator for your user account on %s. \n \n Your new API key can be retrieved by logging in using this sync user's account. \n \n Please update your server's sync setup to reflect this change. \n \n We apologise for the inconvenience. " ,
$baseurl ,
$newkey
);
$this -> sendEmail (
$updatedUser ,
$body ,
$bodyNoEnc ,
__ ( 'API key reset by administrator' )
);
}
return $newkey ;
}
public function extralog ( $user , $action = null , $description = null , $fieldsResult = null , $modifiedUser = null )
{
// new data
$model = 'User' ;
$modelId = $user [ 'id' ];
if ( ! empty ( $modifiedUser )) {
$modelId = $modifiedUser [ 'User' ][ 'id' ];
}
if ( $action == 'login' ) {
$description = " User ( " . $user [ 'id' ] . " ): " . $user [ 'email' ];
} elseif ( $action == 'logout' ) {
$description = " User ( " . $user [ 'id' ] . " ): " . $user [ 'email' ];
} elseif ( $action == 'edit' ) {
$description = " User ( " . $modifiedUser [ 'User' ][ 'id' ] . " ): " . $modifiedUser [ 'User' ][ 'email' ];
} elseif ( $action == 'change_pw' ) {
$description = " User ( " . $modifiedUser [ 'User' ][ 'id' ] . " ): " . $modifiedUser [ 'User' ][ 'email' ];
$fieldsResult = " Password changed. " ;
}
// query
$this -> Log = ClassRegistry :: init ( 'Log' );
2019-09-23 19:07:24 +02:00
$result = $this -> Log -> createLogEntry ( $user , $action , $model , $modelId , $description , $fieldsResult );
2019-09-09 13:00:21 +02:00
// write to syslogd as well
App :: import ( 'Lib' , 'SysLog.SysLog' );
$syslog = new SysLog ();
2019-09-23 19:07:24 +02:00
$syslog -> write ( 'notice' , " $description -- $action " . ( empty ( $fieldResult ) ? '' : ' -- ' . $result [ 'Log' ][ 'change' ]));
2019-09-09 13:00:21 +02:00
}
2019-09-09 13:03:09 +02:00
2019-09-06 21:31:16 +02:00
/**
* @ return Crypt_GPG
* @ throws Exception
*/
private function initializeGpg ()
{
if ( ! class_exists ( 'Crypt_GPG' )) {
if ( ! stream_resolve_include_path ( 'Crypt/GPG.php' )) {
throw new Exception ( " Crypt_GPG is not installed. " );
}
require_once 'Crypt/GPG.php' ;
}
2019-09-07 00:01:55 +02:00
$homedir = Configure :: read ( 'GnuPG.homedir' );
if ( $homedir === null ) {
throw new Exception ( " Configuration option 'GnuPG.homedir' is not set, Crypt_GPG cannot be initialized. " );
}
2019-09-06 21:31:16 +02:00
$options = array (
2019-09-07 00:01:55 +02:00
'homedir' => $homedir ,
2019-09-06 21:31:16 +02:00
'gpgconf' => Configure :: read ( 'GnuPG.gpgconf' ),
'binary' => Configure :: read ( 'GnuPG.binary' ) ? : '/usr/bin/gpg' ,
);
return new Crypt_GPG ( $options );
}
2019-11-16 21:40:02 +01:00
public function getOrgActivity ( $orgId , $params = array ())
{
$conditions = array ();
$options = array ();
foreach ( $params as $paramName => $value ) {
$options [ 'filter' ] = $paramName ;
$filterParam [ $paramName ] = $value ;
$conditions = $this -> Event -> set_filter_timestamp ( $filterParam , $conditions , $options );
}
$conditions [ 'Event.orgc_id' ] = $orgId ;
$events = $this -> Event -> find ( 'all' , array (
'recursive' => - 1 ,
'fields' => array ( 'Event.orgc_id' , 'Event.timestamp' , 'Event.attribute_count' ),
'conditions' => $conditions ,
'order' => 'Event.timestamp'
));
$sparklineData = array ();
foreach ( $events as $event ) {
$date = date ( " Y-m-d " , $event [ 'Event' ][ 'timestamp' ]);
if ( ! isset ( $sparklineData [ $event [ 'Event' ][ 'attribute_count' ]][ $date ])) {
$sparklineData [ $date ] = $event [ 'Event' ][ 'attribute_count' ];
} else {
$sparklineData [ $date ] += $event [ 'Event' ][ 'attribute_count' ];
}
}
// get first and last timestamp
if ( isset ( $params [ 'from' ])) {
$startDate = $params [ 'from' ];
} else {
$startDate = $this -> resolveTimeDelta ( $params [ 'event_timestamp' ]);
}
if ( isset ( $params [ 'to' ])) {
$endDate = $params [ 'to' ];
} else {
$endDate = time ();
}
$dates = array ();
2019-12-11 10:03:43 +01:00
for ( $d = $startDate ; $d < $endDate ; $d = $d + 3600 * 24 ) {
2019-11-16 21:40:02 +01:00
$dates [] = date ( 'Y-m-d' , $d );
}
$csv = 'Date,Close\n' ;
foreach ( $dates as $date ) {
$csv .= sprintf ( '%s,%s\n' , $date , isset ( $sparklineData [ $date ]) ? $sparklineData [ $date ] : 0 );
}
$data = array (
'csv' => $csv ,
'data' => $sparklineData ,
'orgId' => $orgId
);
return $data ;
}
2013-03-06 11:34:22 +01:00
}