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' );
2019-09-30 09:32:36 +02:00
App :: uses ( 'SendEmail' , 'Tools' );
2021-08-24 18:16:32 +02:00
App :: uses ( 'BlowfishConstantPasswordHasher' , 'Controller/Component/Auth' );
2013-03-06 11:34:22 +01:00
2019-09-23 19:07:24 +02:00
/**
* @ property Log $Log
2021-02-09 18:29:35 +01:00
* @ property Organisation $Organisation
* @ property Role $Role
2021-03-20 11:40:41 +01:00
* @ property UserSetting $UserSetting
2021-08-02 10:20:21 +02:00
* @ property Event $Event
2021-08-24 17:35:09 +02:00
* @ property AuthKey $AuthKey
2021-09-30 15:13:34 +02:00
* @ property Server $Server
2019-09-23 19:07:24 +02:00
*/
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 (
2021-06-16 14:03:24 +02:00
'emailValidation' => array (
'rule' => array ( 'validateEmail' ),
2021-08-13 13:53:59 +02:00
'message' => 'Please enter a valid email address.' ,
2018-07-19 11:48:22 +02:00
'required' => true ,
),
'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' ,
2020-10-20 01:43:38 +02:00
'UserSetting' ,
2020-11-24 16:28:10 +01:00
// 'AuthKey' - readd once the initial update storm is over
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 (
2021-01-22 13:01:23 +01:00
'AuditLog' ,
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
2021-08-23 16:09:52 +02:00
public function __construct ( $id = false , $table = null , $ds = null )
{
parent :: __construct ( $id , $table , $ds );
2020-11-24 16:28:10 +01:00
$this -> AdminSetting = ClassRegistry :: init ( 'AdminSetting' );
2021-09-26 18:00:49 +02:00
$db_version = $this -> AdminSetting -> getSetting ( 'db_version' );
if ( $db_version >= 62 ) {
2020-11-24 16:28:10 +01:00
$this -> bindModel ([
'hasMany' => [ 'AuthKey' ]
2020-11-29 12:10:53 +01:00
], false );
2020-11-24 16:28:10 +01:00
}
}
2020-09-08 23:03:48 +02:00
/** @var CryptGpgExtended|null|false */
2020-07-27 19:08:36 +02:00
private $gpg ;
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' ])) {
2021-08-24 18:16:32 +02:00
$passwordHasher = new BlowfishConstantPasswordHasher ();
2018-07-19 11:48:22 +02:00
$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' );
2021-10-10 18:41:44 +02:00
$kafkaTopic = $this -> kafkaTopic ( 'user' );
if ( $pubToZmq || $kafkaTopic ) {
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 );
}
2021-10-10 18:41:44 +02:00
if ( $kafkaTopic ) {
2019-03-05 12:24:56 +01:00
$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
2020-09-08 23:03:48 +02:00
/**
* Checks if the GnuPG key is a valid key .
* @ param array $check
* @ return bool
*/
2018-07-19 11:48:22 +02:00
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
2020-07-27 19:08:36 +02:00
$gpg = $this -> initializeGpg ();
if ( ! $gpg ) {
return true ;
}
2018-07-19 11:48:22 +02:00
try {
2020-09-08 23:03:48 +02:00
$gpgTool = new GpgTool ( $gpg );
$gpgTool -> validateGpgKey ( $check [ 'gpgkey' ]);
return true ;
2018-07-19 11:48:22 +02:00
} catch ( Exception $e ) {
2020-09-08 23:03:48 +02:00
$this -> logException ( " Exception during validating GPG key " , $e , LOG_NOTICE );
2020-07-27 19:08:36 +02:00
return false ;
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
2021-06-16 14:03:24 +02:00
public function validateEmail ( $check )
{
$localPartReg = '[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[\p{L}0-9!#$%&\'*+\/=?^_`{|}~-]+)*@' ;
$domainReg = '[a-z0-9_\-\.]+' ;
$fullReg = sprintf ( '/^%s%s$/ui' , $localPartReg , $domainReg );
$check = array_values ( $check );
$check = $check [ 0 ];
return preg_match ( $fullReg , $check , $matches ) ? true : false ;
}
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
2020-09-08 23:03:48 +02:00
/**
* 0 - true if key is valid
* 1 - User e - mail
* 2 - Error message
* 3 - Not used
* 4 - Key fingerprint
* 5 - Key fingerprint
* @ param array $user
* @ return array
*/
public function verifySingleGPG ( array $user )
2018-07-19 11:48:22 +02:00
{
2020-09-08 23:03:48 +02:00
$result = [ 0 => false , 1 => $user [ 'User' ][ 'email' ]];
$gpg = $this -> initializeGpg ();
if ( ! $gpg ) {
$result [ 2 ] = 'GnuPG is not configured on this system.' ;
return $result ;
2018-07-19 11:48:22 +02:00
}
2020-09-08 23:03:48 +02:00
2018-07-19 11:48:22 +02:00
try {
$currentTimestamp = time ();
2020-09-08 23:03:48 +02:00
$keys = $gpg -> keyInfo ( $user [ 'User' ][ 'gpgkey' ]);
if ( count ( $keys ) !== 1 ) {
$result [ 2 ] = 'Multiple or no key found' ;
return $result ;
}
$key = $keys [ 0 ];
$result [ 4 ] = $key -> getPrimaryKey () -> getFingerprint ();
$result [ 5 ] = $result [ 4 ];
$sortedKeys = [ 'valid' => 0 , 'expired' => 0 , 'noEncrypt' => 0 ];
foreach ( $key -> getSubKeys () as $subKey ) {
2018-07-19 11:48:22 +02:00
$expiration = $subKey -> getExpirationDate ();
if ( $expiration != 0 && $currentTimestamp > $expiration ) {
$sortedKeys [ 'expired' ] ++ ;
continue ;
}
if ( ! $subKey -> canEncrypt ()) {
$sortedKeys [ 'noEncrypt' ] ++ ;
continue ;
}
$sortedKeys [ 'valid' ] ++ ;
}
if ( ! $sortedKeys [ 'valid' ]) {
2021-09-16 16:01:43 +02:00
$result [ 2 ] = 'The user\'s PGP key does not include a valid subkey that could be used for encryption.' ;
2018-07-19 11:48:22 +02:00
if ( $sortedKeys [ 'expired' ]) {
2021-09-16 16:01:43 +02:00
$result [ 2 ] .= ' ' . __n ( 'Found %s subkey that have expired.' , 'Found %s subkeys that have expired.' , $sortedKeys [ 'expired' ], $sortedKeys [ 'expired' ]);
2018-07-19 11:48:22 +02:00
}
if ( $sortedKeys [ 'noEncrypt' ]) {
2021-09-16 16:01:43 +02:00
$result [ 2 ] .= ' ' . __n ( 'Found %s subkey that is sign only.' , 'Found %s subkeys that are sign only.' , $sortedKeys [ 'noEncrypt' ], $sortedKeys [ 'noEncrypt' ]);
2018-07-19 11:48:22 +02:00
}
2020-09-08 23:03:48 +02:00
} else {
2018-07-19 11:48:22 +02:00
$result [ 0 ] = true ;
}
} catch ( Exception $e ) {
$result [ 2 ] = $e -> getMessage ();
}
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' );
$conditions = array ( 'not' => array ( 'gpgkey' => '' ));
if ( $id !== false ) {
$conditions [ 'User.id' ] = $id ;
}
$users = $this -> find ( 'all' , array (
'conditions' => $conditions ,
2021-09-16 16:01:43 +02:00
'fields' => [ 'id' , 'email' , 'gpgkey' ],
2018-07-19 11:48:22 +02:00
'recursive' => - 1 ,
));
if ( empty ( $users )) {
2020-07-27 19:08:36 +02:00
return [];
2018-07-19 11:48:22 +02:00
}
2019-09-06 21:31:16 +02:00
$gpg = $this -> initializeGpg ();
2020-07-27 19:08:36 +02:00
if ( ! $gpg ) {
return [];
}
$results = [];
2021-09-16 16:01:43 +02:00
foreach ( $users as $user ) {
2020-09-08 23:03:48 +02:00
$results [ $user [ 'User' ][ 'id' ]] = $this -> verifySingleGPG ( $user );
2018-07-19 11:48:22 +02:00
}
return $results ;
}
2016-04-04 19:23:05 +02:00
2018-08-20 10:50:09 +02:00
private function testSmimeCertificate ( $certif_public )
{
2020-04-25 14:59:37 +02:00
$sendEmail = new SendEmail ();
2018-08-03 17:23:06 +02:00
try {
2020-04-25 14:59:37 +02:00
$sendEmail -> testSmimeCertificate ( $certif_public );
return true ;
2018-08-03 17:23:06 +02:00
} catch ( Exception $e ) {
2020-04-25 14:59:37 +02:00
if ( $e -> getPrevious ()) {
return $e -> getMessage () . " : " . $e -> getPrevious () -> getMessage ();
}
return $e -> getMessage ();
2018-08-03 17:23:06 +02:00
}
}
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
2020-08-21 15:43:27 +02:00
/**
* If you want to check if user has GPG or X . 509 or send encrypted emails to that user , you need user keys . But by
* default , keys are part of default user model . This method add that keys to user model .
*
* @ param array $user
* @ return array
* @ throws Exception
*/
public function fillKeysToUser ( array $user )
2018-07-19 11:48:22 +02:00
{
2020-08-21 15:43:27 +02:00
if ( empty ( $user [ 'id' ])) {
throw new InvalidArgumentException ( " Invalid user model provided, not ID found. " );
}
2018-07-19 11:48:22 +02:00
$result = $this -> find ( 'first' , array (
'recursive' => - 1 ,
2020-08-21 15:43:27 +02:00
'fields' => array ( 'certif_public' , 'gpgkey' ),
'conditions' => array ( 'id' => $user [ 'id' ]),
2018-07-19 11:48:22 +02:00
));
2020-08-21 15:43:27 +02:00
if ( ! $result ) {
throw new Exception ( " User with ID { $user [ 'id' ] } not found. " );
}
$user [ 'gpgkey' ] = $result [ 'User' ][ 'gpgkey' ];
$user [ 'certif_public' ] = $result [ 'User' ][ 'certif_public' ];
return $user ;
2018-07-19 11:48:22 +02:00
}
2016-05-31 17:36:06 +02:00
2020-08-20 19:47:27 +02:00
/**
* @ param int $id
* @ return array | null
*/
public function getUserById ( $id )
2018-07-19 11:48:22 +02:00
{
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
}
2020-08-20 19:47:27 +02:00
return $this -> find (
2019-10-15 07:24:43 +02:00
'first' ,
array (
2020-08-20 19:47:27 +02:00
'conditions' => array ( 'User.id' => $id ),
2019-10-15 07:24:43 +02:00
'recursive' => - 1 ,
'contain' => array (
'Organisation' ,
'Role' ,
'Server' ,
2020-08-20 19:47:27 +02:00
'UserSetting' ,
2019-10-15 07:24:43 +02:00
)
)
);
2020-08-20 19:47:27 +02:00
}
2021-09-14 18:52:28 +02:00
/**
* Get the current user and rearrange it to be in the same format as in the auth component .
* @ param int $id
* @ param bool $full
* @ return array | null
*/
public function getAuthUser ( $id , $full = false )
2020-08-20 19:47:27 +02:00
{
2020-11-16 09:46:26 +01:00
if ( empty ( $id )) {
throw new InvalidArgumentException ( 'Invalid user ID.' );
2018-07-19 11:48:22 +02:00
}
2020-11-16 09:46:26 +01:00
$conditions = [ 'User.id' => $id ];
2021-09-14 18:52:28 +02:00
return $this -> getAuthUserByConditions ( $conditions , $full );
2018-07-19 11:48:22 +02:00
}
2016-06-04 01:08:16 +02:00
2021-09-14 18:52:28 +02:00
/**
* Get the current user and rearrange it to be in the same format as in the auth component .
* @ param string $authkey
* @ return array | null
*/
2020-11-16 09:46:26 +01:00
public function getAuthUserByAuthkey ( $authkey )
2018-07-19 11:48:22 +02:00
{
2020-11-16 09:46:26 +01:00
if ( empty ( $authkey )) {
throw new InvalidArgumentException ( 'Invalid user auth key.' );
2018-07-19 11:48:22 +02:00
}
2020-11-16 09:46:26 +01:00
$conditions = array ( 'User.authkey' => $authkey );
return $this -> getAuthUserByConditions ( $conditions );
2018-07-19 11:48:22 +02:00
}
2016-06-04 01:08:16 +02:00
2021-09-14 18:52:28 +02:00
/**
* @ param string $auth_key
* @ return array | null
*/
2018-07-19 11:48:22 +02:00
public function getAuthUserByExternalAuth ( $auth_key )
{
2020-11-16 09:46:26 +01:00
if ( empty ( $auth_key )) {
throw new InvalidArgumentException ( 'Invalid user external auth key.' );
}
2018-07-19 11:48:22 +02:00
$conditions = array (
'User.external_auth_key' => $auth_key ,
'User.external_auth_required' => true
);
2020-11-16 09:46:26 +01:00
return $this -> getAuthUserByConditions ( $conditions );
}
/**
2021-09-14 18:52:28 +02:00
* Get user model with Role , Organisation and Server , but without PGP and S / MIME keys
2020-11-16 09:46:26 +01:00
* @ param array $conditions
2021-09-14 18:52:28 +02:00
* @ param bool $full When true , fetch all user fields .
2020-11-16 09:46:26 +01:00
* @ return array | null
*/
2021-09-14 18:52:28 +02:00
private function getAuthUserByConditions ( array $conditions , $full = false )
2020-11-16 09:46:26 +01:00
{
$user = $this -> find ( 'first' , [
2018-07-19 11:48:22 +02:00
'conditions' => $conditions ,
2021-09-14 18:52:28 +02:00
'fields' => $full ? [] : $this -> describeAuthFields (),
2018-07-19 11:48:22 +02:00
'recursive' => - 1 ,
2020-11-16 09:46:26 +01:00
'contain' => [
2018-07-19 11:48:22 +02:00
'Organisation' ,
'Role' ,
2020-11-16 09:46:26 +01:00
'Server' ,
],
]);
2018-07-19 11:48:22 +02:00
if ( empty ( $user )) {
return $user ;
}
2020-08-20 19:47:27 +02:00
return $this -> rearrangeToAuthForm ( $user );
}
/**
* User model is a mess . Sometimes it is necessary to convert User model to form that is created during the login
* process . This method do that work for you .
*
* @ param array $user
* @ return array
*/
public function rearrangeToAuthForm ( array $user )
{
if ( ! isset ( $user [ 'User' ])) {
throw new InvalidArgumentException ( 'Invalid user model provided.' );
}
2018-07-19 11:48:22 +02:00
$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
// 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 ())
{
$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 ) {
2020-08-12 18:11:24 +02:00
$sgModel = ClassRegistry :: init ( 'SharingGroup' );
2018-07-19 11:48:22 +02:00
$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 ,
2020-08-12 18:11:24 +02:00
'fields' => array ( 'id' , 'email' , 'gpgkey' , 'certif_public' , 'org_id' , 'disabled' ),
2021-02-05 15:40:22 +01:00
'contain' => [ 'Role' => [ 'fields' => [ 'perm_site_admin' , 'perm_audit' ]], 'Organisation' => [ 'fields' => [ 'id' , 'name' ]]],
2018-07-19 11:48:22 +02:00
));
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
2020-07-27 19:08:36 +02:00
/**
* @ param array $params
2021-09-14 14:55:54 +02:00
* @ return array | bool
2020-07-27 19:08:36 +02:00
* @ throws Crypt_GPG_Exception
* @ throws SendEmailException
*/
2021-09-14 14:55:54 +02:00
public function sendEmailExternal ( array $params )
2019-08-29 09:24:33 +02:00
{
2020-07-27 19:08:36 +02:00
$gpg = $this -> initializeGpg ();
2019-09-30 09:32:36 +02:00
$sendEmail = new SendEmail ( $gpg );
2021-09-14 14:55:54 +02:00
return $sendEmail -> sendExternal ( $params );
2019-08-29 09:24:33 +02:00
}
2020-08-30 16:49:33 +02:00
/**
* All e - mail sending is now handled by this method
* Just pass the user 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
*
* @ param array $user
2021-02-06 14:24:09 +01:00
* @ param SendEmailTemplate | string $body
* @ param string | false $bodyNoEnc
2020-08-30 16:49:33 +02:00
* @ param string $subject
* @ param array | false $replyToUser
* @ return bool
* @ throws Crypt_GPG_BadPassphraseException
* @ throws Crypt_GPG_Exception
*/
public function sendEmail ( array $user , $body , $bodyNoEnc = false , $subject , $replyToUser = false )
2018-07-19 11:48:22 +02:00
{
2019-09-30 09:32:36 +02:00
if ( $user [ 'User' ][ 'disabled' ]) {
2018-07-19 11:48:22 +02:00
return true ;
}
2016-04-04 19:23:05 +02:00
2021-02-05 10:44:10 +01:00
$this -> loadLog ();
2020-04-25 14:59:01 +02:00
$replyToLog = $replyToUser ? ' from ' . $replyToUser [ 'User' ][ 'email' ] : '' ;
2016-06-04 01:08:16 +02:00
2020-07-27 19:08:36 +02:00
$gpg = $this -> initializeGpg ();
2019-09-30 09:32:36 +02:00
$sendEmail = new SendEmail ( $gpg );
try {
2021-02-07 19:40:33 +01:00
$result = $sendEmail -> sendToUser ( $user , $subject , $body , $bodyNoEnc , $replyToUser ? : []);
2019-09-30 09:32:36 +02:00
} catch ( SendEmailException $e ) {
2020-04-25 14:59:01 +02:00
$this -> logException ( " Exception during sending e-mail " , $e );
2019-09-30 09:32:36 +02:00
$this -> Log -> create ();
2020-05-07 17:11:42 +02:00
$this -> Log -> save ( array (
'org' => 'SYSTEM' ,
'model' => 'User' ,
'model_id' => $user [ 'User' ][ 'id' ],
'email' => $user [ 'User' ][ 'email' ],
2019-09-30 09:32:36 +02:00
'action' => 'email' ,
2020-04-25 14:59:01 +02:00
'title' => 'Email' . $replyToLog . ' to ' . $user [ 'User' ][ 'email' ] . ', titled "' . $subject . '" failed. Reason: ' . $e -> getMessage (),
2020-05-07 17:11:42 +02:00
'change' => null ,
));
2019-09-30 09:32:36 +02:00
return false ;
2020-05-07 17:11:42 +02:00
}
2018-08-03 18:26:45 +02:00
2021-02-07 19:40:33 +01:00
$logTitle = $result [ 'encrypted' ] ? 'Encrypted email' : 'Email' ;
2020-08-11 14:53:35 +02:00
// Intentional two spaces to pass test :)
2021-02-07 19:40:33 +01:00
$logTitle .= $replyToLog . ' to ' . $user [ 'User' ][ 'email' ] . ' sent, titled "' . $result [ 'subject' ] . '".' ;
2020-04-25 14:59:01 +02:00
2019-09-30 09:32:36 +02:00
$this -> Log -> create ();
$this -> Log -> save ( array (
'org' => 'SYSTEM' ,
'model' => 'User' ,
'model_id' => $user [ 'User' ][ 'id' ],
'email' => $user [ 'User' ][ 'email' ],
'action' => 'email' ,
2020-04-25 14:59:01 +02:00
'title' => $logTitle ,
2019-09-30 09:32:36 +02:00
'change' => null ,
));
2018-08-03 18:26:45 +02:00
return true ;
}
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
{
2020-09-08 23:03:48 +02:00
$gpgTool = new GpgTool ( null );
2019-09-23 20:56:52 +02:00
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
{
2020-09-08 23:03:48 +02:00
$gpgTool = new GpgTool ( $this -> initializeGpg ());
2019-09-23 20:56:52 +02:00
return $gpgTool -> fetchGpgKey ( $fingerprint );
2018-07-19 11:48:22 +02:00
}
2016-06-04 01:08:16 +02:00
2020-11-26 20:27:35 +01:00
/**
* Returns fields that should be fetched from database .
* @ return array
*/
2018-07-19 11:48:22 +02:00
public function describeAuthFields ()
{
2020-11-26 20:27:35 +01:00
$fields = $this -> schema ();
// Do not include keys, because they are big and usually not necessary
unset ( $fields [ 'gpgkey' ]);
unset ( $fields [ 'certif_public' ]);
2021-09-15 09:55:46 +02:00
// Do not fetch password from db, it is automatically fetched by BaseAuthenticate::_findUser
unset ( $fields [ 'password' ]);
2021-09-16 10:43:57 +02:00
// Do not fetch authkey from db, it is sensitive and not need
unset ( $fields [ 'authkey' ]);
2020-11-26 20:27:35 +01:00
$fields = array_keys ( $fields );
foreach ( $this -> belongsTo as $relatedModel => $foo ) {
2018-07-19 11:48:22 +02:00
$fields [] = $relatedModel . '.*' ;
}
return $fields ;
}
2016-08-22 02:52:51 +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' );
2020-06-05 13:47:16 +02:00
$options = array ( 'newUserText' , 'passwordResetText' );
2018-07-19 11:48:22 +02:00
$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 );
2021-04-28 15:33:50 +02:00
$body = str_replace ( '\n' , PHP_EOL , $body );
2018-07-19 11:48:22 +02:00
$result = $this -> sendEmail ( $user , $body , false , $subject );
if ( $result ) {
$this -> id = $user [ 'User' ][ 'id' ];
$this -> saveField ( 'password' , $password );
2020-11-12 19:01:42 +01:00
$this -> updateField ( $user [ 'User' ], 'change_pw' , 1 );
2018-07-19 11:48:22 +02:00
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 )
{
2020-12-27 15:37:31 +01:00
$adminRoles = $this -> Role -> find ( 'column' , array (
2018-07-19 11:48:22 +02:00
'conditions' => array ( 'perm_admin' => 1 ),
2020-12-27 15:37:31 +01:00
'fields' => array ( 'Role.id' )
2018-07-19 11:48:22 +02:00
));
$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 {
2021-08-24 18:16:32 +02:00
$passwordHasher = new BlowfishConstantPasswordHasher ();
2018-07-19 11:48:22 +02:00
}
$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 );
2021-07-26 16:12:04 +02:00
if ( ! empty ( Configure :: read ( " Security.advanced_authkeys " ))) {
$this -> AuthKey = ClassRegistry :: init ( 'AuthKey' );
$newKey = [
'authkey' => $authKey ,
'user_id' => 1 ,
'comment' => 'Initial auto-generated key' ,
'allowed_ips' => null ,
];
$this -> AuthKey -> create ();
$this -> AuthKey -> save ( $newKey );
}
2018-07-19 11:48:22 +02:00
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 ();
$data = array (
2021-03-10 21:33:10 +01:00
'worker' => 'prio' ,
'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.' ,
2019-09-09 13:00:21 +02:00
);
$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 ;
}
2021-03-01 15:25:18 +01:00
public function resetauthkey ( $user , $id , $alert = false , $keyId = null )
2019-09-09 13:00:21 +02:00
{
$this -> id = $id ;
if ( ! $id || ! $this -> exists ( $id )) {
return false ;
}
$updatedUser = $this -> read ();
if ( empty ( $user [ 'Role' ][ 'perm_site_admin' ]) && ! ( $user [ 'Role' ][ 'perm_admin' ] && $user [ 'org_id' ] == $updatedUser [ 'User' ][ 'org_id' ]) && ( $user [ 'id' ] != $id )) {
return false ;
}
2020-11-13 12:54:14 +01:00
if ( empty ( Configure :: read ( 'Security.advanced_authkeys' ))) {
$oldKey = $this -> data [ 'User' ][ 'authkey' ];
$newkey = $this -> generateAuthKey ();
2020-11-12 19:01:42 +01:00
$this -> updateField ( $updatedUser [ 'User' ], 'authkey' , $newkey );
2020-11-13 12:54:14 +01:00
$this -> extralog (
$user ,
'reset_auth_key' ,
2021-03-01 15:25:18 +01:00
__ ( 'Authentication key for user %s (%s) updated.' ,
2020-11-13 12:54:14 +01:00
$updatedUser [ 'User' ][ 'id' ],
$updatedUser [ 'User' ][ 'email' ]
),
$fieldsResult = [ 'authkey' => [ $oldKey , $newkey ]],
$updatedUser
);
} else {
$this -> AuthKey = ClassRegistry :: init ( 'AuthKey' );
2021-03-01 15:25:18 +01:00
$newkey = $this -> AuthKey -> resetAuthKey ( $id , $keyId );
2020-11-13 12:54:14 +01:00
}
2019-09-09 13:00:21 +02:00
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 ();
2021-08-23 16:09:52 +02:00
$syslog -> write ( 'notice' , " $description -- $action " . ( empty ( $fieldsResult ) ? '' : ' -- ' . $result [ 'Log' ][ 'change' ]));
2019-09-09 13:00:21 +02:00
}
2019-09-09 13:03:09 +02:00
2019-09-27 18:48:59 +02:00
/**
* @ return array | null
* @ throws Exception
*/
public function getGpgPublicKey ()
{
$email = Configure :: read ( 'GnuPG.email' );
if ( ! $email ) {
throw new Exception ( " Configuration option 'GnuPG.email' is not set, public key cannot be exported. " );
}
$cryptGpg = $this -> initializeGpg ();
$fingerprint = $cryptGpg -> getFingerprint ( $email );
if ( ! $fingerprint ) {
return null ;
}
$publicKey = $cryptGpg -> exportPublicKey ( $fingerprint );
return array ( $fingerprint , $publicKey );
}
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 ;
}
2020-03-25 11:49:33 +01:00
2020-04-07 13:21:01 +02:00
public function registerUser ( $added_by , $registration , $org_id , $role_id ) {
$user = array (
'email' => $registration [ 'data' ][ 'email' ],
'gpgkey' => empty ( $registration [ 'data' ][ 'pgp' ]) ? '' : $registration [ 'data' ][ 'pgp' ],
'disabled' => 0 ,
'newsread' => 0 ,
'change_pw' => 1 ,
'authkey' => $this -> generateAuthKey (),
'termsaccepted' => 0 ,
'org_id' => $org_id ,
'role_id' => $role_id ,
'invited_by' => $added_by [ 'id' ],
'contactalert' => 1 ,
'autoalert' => Configure :: check ( 'MISP.default_publish_alert' ) ? Configure :: read ( 'MISP.default_publish_alert' ) : 1
);
$this -> create ();
$this -> Log = ClassRegistry :: init ( 'Log' );
$result = $this -> save ( array ( 'User' => $user ));
2020-04-07 14:27:21 +02:00
$currentOrg = $this -> Organisation -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array ( 'Organisation.id' => $org_id )
));
if ( ! empty ( $currentOrg ) && empty ( $currentOrg [ 'Organisation' ][ 'local' ])) {
$currentOrg [ 'Organisation' ][ 'local' ] = 1 ;
$this -> Organisation -> save ( $currentOrg );
}
2020-04-07 13:21:01 +02:00
if ( empty ( $result )) {
$error = array ();
foreach ( $this -> validationErrors as $key => $errors ) {
$error [ $key ] = $key . ': ' . implode ( ', ' , $errors );
}
$error = implode ( PHP_EOL , $error );
$this -> Log -> save ( array (
'org' => 'SYSTEM' ,
'model' => 'User' ,
'model_id' => $added_by [ 'id' ],
'email' => $added_by [ 'email' ],
2020-05-07 15:56:08 +02:00
'action' => 'registration_error' ,
2020-04-07 13:21:01 +02:00
'title' => 'User registration failed for ' . $user [ 'email' ] . '. Reason(s): ' . $error ,
'change' => null ,
));
return false ;
} else {
$user = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'conditions' => array ( 'id' => $this -> id )
));
2020-04-22 10:04:07 +02:00
$this -> Log -> save ( array (
'org' => 'SYSTEM' ,
'model' => 'User' ,
'model_id' => $added_by [ 'id' ],
'email' => $added_by [ 'email' ],
2020-05-07 15:56:08 +02:00
'action' => 'registration' ,
2020-04-22 10:04:07 +02:00
'title' => sprintf ( 'User registration success for %s (id=%s)' , $user [ 'User' ][ 'email' ], $user [ 'User' ][ 'id' ]),
'change' => null ,
));
2020-04-07 13:21:01 +02:00
$this -> initiatePasswordReset ( $user , true , true , false );
$this -> Inbox = ClassRegistry :: init ( 'Inbox' );
$this -> Inbox -> delete ( $registration [ 'id' ]);
return true ;
}
2020-07-27 19:08:36 +02:00
}
2020-10-29 17:07:47 +01:00
/**
* Updates `current_login` and `last_login` time in database .
*
* @ param array $user
* @ return array | bool
* @ throws Exception
*/
public function updateLoginTimes ( array $user )
{
if ( ! isset ( $user [ 'id' ])) {
throw new InvalidArgumentException ( " Invalid user object provided. " );
}
$user [ 'action' ] = 'login' ; // for afterSave callbacks
$user [ 'last_login' ] = $user [ 'current_login' ];
$user [ 'current_login' ] = time ();
return $this -> save ( $user , true , array ( 'id' , 'last_login' , 'current_login' ));
}
2020-11-19 18:11:19 +01:00
/**
* Update field in user model and also set `date_modified`
*
* @ param array $user
* @ param string $name
* @ param mixed $value
* @ throws Exception
*/
public function updateField ( array $user , $name , $value )
{
if ( ! isset ( $user [ 'id' ])) {
throw new InvalidArgumentException ( " Invalid user object provided. " );
}
$success = $this -> save ([
'id' => $user [ 'id' ],
$name => $value ,
], true , [ 'id' , $name , 'date_modified' ]);
if ( ! $success ) {
2020-11-12 19:01:42 +01:00
throw new RuntimeException ( " Could not save setting $name for user { $user [ 'id' ] } . " );
2020-11-19 18:11:19 +01:00
}
}
2020-07-27 19:08:36 +02:00
/**
* Initialize GPG . Returns `null` if initialization failed .
*
2020-09-08 23:03:48 +02:00
* @ return null | CryptGpgExtended
2020-07-27 19:08:36 +02:00
*/
private function initializeGpg ()
{
if ( $this -> gpg !== null ) {
if ( $this -> gpg === false ) { // initialization failed
return null ;
}
return $this -> gpg ;
}
2020-04-07 13:21:01 +02:00
2020-07-27 19:08:36 +02:00
try {
2020-09-08 23:03:48 +02:00
$this -> gpg = GpgTool :: initializeGpg ();
2020-07-27 19:08:36 +02:00
return $this -> gpg ;
} catch ( Exception $e ) {
$this -> logException ( " GPG couldn't be initialized, GPG encryption and signing will be not available. " , $e , LOG_NOTICE );
$this -> gpg = false ;
return null ;
}
2020-04-07 13:21:01 +02:00
}
2021-07-16 15:13:55 +02:00
public function updateToAdvancedAuthKeys ()
{
$users = $this -> find ( 'all' , [
'recursive' => - 1 ,
'contain' => [ 'AuthKey' ],
'fields' => [ 'id' , 'authkey' ]
]);
$updated = 0 ;
foreach ( $users as $user ) {
if ( ! empty ( $user [ 'AuthKey' ])) {
$currentKeyStart = substr ( $user [ 'User' ][ 'authkey' ], 0 , 4 );
$currentKeyEnd = substr ( $user [ 'User' ][ 'authkey' ], - 4 );
foreach ( $user [ 'AuthKey' ] as $authkey ) {
if ( $authkey [ 'authkey_start' ] === $currentKeyStart && $authkey [ 'authkey_end' ] === $currentKeyEnd ) {
continue 2 ;
}
}
}
$this -> AuthKey -> create ();
$this -> AuthKey -> save ([
'authkey' => $user [ 'User' ][ 'authkey' ],
'expiration' => 0 ,
'user_id' => $user [ 'User' ][ 'id' ]
]);
$updated += 1 ;
}
return $updated ;
}
2021-08-31 09:39:05 +02:00
public function checkNotificationBanStatus ( array $user )
{
$banStatus = [
'error' => false ,
'active' => false ,
'message' => __ ( 'User is not banned to sent email notification' )
];
if ( ! empty ( $user [ 'Role' ][ 'perm_site_admin' ])) {
return $banStatus ;
}
if ( Configure :: read ( 'MISP.user_email_notification_ban' )) {
$banThresholdAmount = intval ( Configure :: read ( 'MISP.user_email_notification_ban_amount_threshold' ));
$banThresholdMinutes = intval ( Configure :: read ( 'MISP.user_email_notification_ban_time_threshold' ));
$banThresholdSeconds = 60 * $banThresholdMinutes ;
$redis = $this -> setupRedis ();
if ( $redis === false ) {
$banStatus [ 'error' ] = true ;
$banStatus [ 'active' ] = true ;
$banStatus [ 'message' ] = __ ( 'Reason: Could not reach redis to chech user email notification ban status.' );
return $banStatus ;
}
$redisKeyAmountThreshold = " misp:user_email_notification_ban_amount: { $user [ 'id' ] } " ;
$notificationAmount = $redis -> get ( $redisKeyAmountThreshold );
if ( ! empty ( $notificationAmount )) {
$remainingAttempt = $banThresholdAmount - intval ( $notificationAmount );
if ( $remainingAttempt <= 0 ) {
$ttl = $redis -> ttl ( $redisKeyAmountThreshold );
$remainingMinutes = intval ( $ttl ) / 60 ;
$banStatus [ 'active' ] = true ;
$banStatus [ 'message' ] = __ ( 'Reason: User is banned from sending out emails (%s notification tried to be sent). Ban will be lifted in %smin %ssec.' , $notificationAmount , floor ( $remainingMinutes ), intval ( $ttl ) % 60 );
}
}
$pipe = $redis -> multi ( Redis :: PIPELINE )
-> incr ( $redisKeyAmountThreshold );
2021-09-07 09:01:41 +02:00
if ( ! $banStatus [ 'active' ]) { // no need to refresh the ttl if the ban is active
2021-08-31 09:39:05 +02:00
$pipe -> expire ( $redisKeyAmountThreshold , $banThresholdSeconds );
}
$pipe -> exec ();
return $banStatus ;
}
$banStatus [ 'message' ] = __ ( 'User email notification ban setting is not enabled' );
return $banStatus ;
}
2013-03-06 11:34:22 +01:00
}