2013-03-06 11:34:22 +01:00
< ? php
App :: uses ( 'AppModel' , 'Model' );
App :: uses ( 'AuthComponent' , 'Controller/Component' );
/**
* User Model
*
* @ property Role $Role
* @ property Event $Event
*/
class User extends AppModel {
/**
* Display field
*
* @ var string
*/
public $displayField = 'email' ;
public $orgField = 'org' ; // TODO Audit, LogableBehaviour + org
2013-08-21 11:33:30 +02:00
2013-03-06 11:34:22 +01:00
/**
* Validation rules
*
* @ var array
*/
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 (
2015-01-27 10:41:43 +01:00
'rule' => array ( 'passwordLength' ),
'message' => 'Password length requirement not met.' ,
2013-03-06 11:34:22 +01:00
//'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' ),
2015-01-27 10:41:43 +01:00
'message' => 'Password complexity requirement not met.' ,
2013-03-06 11:34:22 +01:00
//'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
),
),
'org' => array (
'notempty' => array (
'rule' => array ( 'notempty' ),
'message' => 'Please specify the organisation where you are working.' ,
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'org_id' => array (
'notempty' => array (
'rule' => array ( 'notempty' ),
2013-04-29 10:52:07 +02:00
'message' => 'Please specify the organisation ID where you are working.' ,
2013-03-06 11:34:22 +01:00
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'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' => true ,
'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' => true ,
'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 ,
),
'notempty' => array (
'rule' => array ( 'notempty' ),
//'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
),
),
'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 (
'numeric' => array (
'rule' => array ( 'numeric' ),
//'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 (
'notempty' => array (
'rule' => array ( 'validateGpgkey' ),
'message' => 'GPG key not valid, please enter a valid key.' ,
//'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 (
'date' => array (
'rule' => array ( 'date' ),
//'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
),
),
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
* belongsTo associations
*
* @ var array
*/
public $belongsTo = array (
'Role' => array (
'className' => 'Role' ,
'foreignKey' => 'role_id' ,
'conditions' => '' ,
'fields' => '' ,
'order' => ''
)
);
/**
* hasMany associations
*
* @ var array
*/
public $hasMany = array (
'Event' => array (
'className' => 'Event' ,
'foreignKey' => 'user_id' ,
'dependent' => false ,
'conditions' => '' ,
'fields' => '' ,
'order' => '' ,
'limit' => '' ,
'offset' => '' ,
'exclusive' => '' ,
'finderQuery' => '' ,
'counterQuery' => ''
2013-08-14 17:46:57 +02:00
),
'Post' => array (
2013-03-06 11:34:22 +01:00
)
);
public $actsAs = array (
'SysLogLogable.SysLogLogable' => array ( // TODO Audit, logable
'userModel' => 'User' ,
'userKey' => 'user_id' ,
'change' => 'full'
),
'Trim' ,
2013-08-21 11:33:30 +02:00
'Containable'
2013-03-06 11:34:22 +01:00
//'RemoveNewline' => array('fields' => array('gpgkey')),
);
2013-05-13 14:27:40 +02:00
public function beforeSave ( $options = array ()) {
2013-03-06 11:34:22 +01:00
if ( isset ( $this -> data [ $this -> alias ][ 'password' ])) {
$this -> data [ $this -> alias ][ 'password' ] = AuthComponent :: password ( $this -> data [ $this -> alias ][ 'password' ]);
}
return true ;
// only accept add and edit in own org
//if ($this->data[$this->alias]['org'] != "TEST") {
// return false;
//}
//return true;
}
/**
* Checks if the GPG key is a valid key
* But also import it in the keychain .
*/
2014-01-21 11:28:18 +01:00
// TODO: this will NOT fail on keys that can only be used for signing but not encryption!
// the method in verifyUsers will fail in that case.
2013-03-06 11:34:22 +01:00
public function validateGpgkey ( $check ) {
// LATER first remove the old gpgkey from the keychain
// empty value
if ( empty ( $check [ 'gpgkey' ])) {
return true ;
}
// we have a clean, hopefull public, key here
// key is entered
require_once 'Crypt/GPG.php' ;
try {
$gpg = new Crypt_GPG ( array ( 'homedir' => Configure :: read ( 'GnuPG.homedir' )));
try {
$keyImportOutput = $gpg -> importKey ( $check [ 'gpgkey' ]);
if ( ! empty ( $keyImportOutput [ 'fingerprint' ])) {
return true ;
}
} catch ( Exception $e ) {
//debug($e);
2013-07-17 12:54:55 +02:00
$this -> log ( $e -> getMessage ());
2013-03-06 11:34:22 +01:00
return false ;
}
} catch ( Exception $e ) {
//debug($e);
2013-07-17 12:54:55 +02:00
$this -> log ( $e -> getMessage ());
2013-03-06 11:34:22 +01:00
return true ; // TODO was false
}
}
2015-01-27 10:41:43 +01:00
public function passwordLength ( $check ) {
$length = Configure :: read ( 'Security.password_policy_length' );
if ( empty ( $length ) || $length < 0 ) $length = 6 ;
$value = array_values ( $check );
$value = $value [ 0 ];
if ( strlen ( $value ) < $length ) return false ;
return true ;
}
2013-03-06 11:34:22 +01:00
public function complexPassword ( $check ) {
/*
2015-01-27 10:41:43 +01:00
default password :
2013-03-06 11:34:22 +01:00
6 characters minimum
1 or more upper - case letters
1 or more lower - case letters
1 or more digits or special characters
example : " EasyPeasy34 "
2015-01-27 10:41:43 +01:00
If Security . password_policy_complexity is set and valid , use the regex provided .
2013-03-06 11:34:22 +01:00
*/
2015-01-27 10:41:43 +01:00
$regex = Configure :: read ( 'Security.password_policy_complexity' );
if ( empty ( $regex ) || @ preg_match ( $regex , 'test' ) === false ) $regex = '/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/' ;
2013-03-06 11:34:22 +01:00
$value = array_values ( $check );
$value = $value [ 0 ];
2015-01-27 10:41:43 +01:00
return preg_match ( $regex , $value );
2013-03-06 11:34:22 +01:00
}
public function identicalFieldValues ( $field = array (), $compareField = null ) {
foreach ( $field as $key => $value ) {
$v1 = $value ;
$v2 = $this -> data [ $this -> name ][ $compareField ];
if ( $v1 !== $v2 ) {
return false ;
} else {
continue ;
}
}
return true ;
}
/**
* Generates an authentication key for each user
*/
public function generateAuthKey () {
$length = 40 ;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ;
$charLen = strlen ( $characters ) - 1 ;
$key = '' ;
for ( $p = 0 ; $p < $length ; $p ++ ) {
$key .= $characters [ rand ( 0 , $charLen )];
}
2013-07-11 14:26:28 +02:00
return $key ;
}
2013-03-06 11:34:22 +01:00
2013-07-11 14:30:56 +02:00
public function generateRandomPassword () {
2013-07-11 14:26:28 +02:00
$length = 12 ;
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-+=!@#$%&*()<>/?' ;
$charLen = strlen ( $characters ) - 1 ;
$key = '' ;
for ( $p = 0 ; $p < $length ; $p ++ ) {
$key .= $characters [ rand ( 0 , $charLen )];
}
2013-03-06 11:34:22 +01:00
return $key ;
}
2013-07-11 14:26:28 +02:00
2013-03-06 11:34:22 +01:00
public function checkAndCorrectPgps () {
$fails = array ();
$users = $this -> find ( 'all' , array ( 'recursive' => 0 ));
foreach ( $users as $user ) {
if ( strlen ( $user [ 'User' ][ 'gpgkey' ]) && strpos ( $user [ 'User' ][ 'gpgkey' ], " \n " )) {
$fails [] = $user [ 'User' ][ 'id' ] . ':' . $user [ 'User' ][ 'id' ];
//$check['gpgkey'] = trim(preg_replace('/\n', '', $check['gpgkey']));
}
}
return $fails ;
}
2014-01-03 15:26:35 +01:00
public function getOrgs () {
$this -> recursive = - 1 ;
$orgs = $this -> find ( 'all' , array (
'fields' => array ( 'DISTINCT (User.org) AS org' ),
));
$orgNames = array ();
foreach ( $orgs as $org ) {
$orgNames [] = $org [ 'User' ][ 'org' ];
}
return $orgNames ;
}
2014-01-21 11:28:18 +01:00
public function getOrgMemberCount ( $org ) {
return $this -> find ( 'count' , array (
'conditions' => array (
'org =' => $org ,
)));
}
public function verifyGPG () {
require_once 'Crypt/GPG.php' ;
$this -> Behaviors -> detach ( 'Trim' );
$results = array ();
$users = $this -> find ( 'all' , array (
'conditions' => array ( 'not' => array ( 'gpgkey' => '' )),
//'fields' => array('id', 'email', 'gpgkey'),
'recursive' => - 1 ,
));
foreach ( $users as $k => $user ) {
2014-09-08 13:34:14 +02:00
$gpg = new Crypt_GPG ( array ( 'homedir' => Configure :: read ( 'GnuPG.homedir' )));
2014-01-21 11:28:18 +01:00
$key = $gpg -> importKey ( $user [ 'User' ][ 'gpgkey' ]);
$gpg -> addEncryptKey ( $key [ 'fingerprint' ]); // use the key that was given in the import
try {
$enc = $gpg -> encrypt ( 'test' , true );
} catch ( Exception $e ){
$results [ $user [ 'User' ][ 'id' ]][ 0 ] = true ;
}
$results [ $user [ 'User' ][ 'id' ]][ 1 ] = $user [ 'User' ][ 'email' ];
}
return $results ;
}
2014-04-01 16:20:47 +02:00
public function getPGP ( $id ) {
$result = $this -> find ( 'first' , array (
'recursive' => - 1 ,
'fields' => array ( 'id' , 'gpgkey' ),
2014-08-21 15:27:25 +02:00
'conditions' => array ( 'id' => $id ),
2014-04-01 16:20:47 +02:00
));
return $result [ 'User' ][ 'gpgkey' ];
}
Rework of the e-mailing, part 1
- Reworking the way e-mails are sent - all of it goes through a centralised e-mail method
- just pass the recipient, recipient encryption key collection, body, alternate body if the message cannot be encrypted, subject, reply to address and pgp key for reply to along and the method will do the rest
- encrypt if possible, check if sending without encryption is allowed, signing, adding attachment for reply to encryption key, using alternate sanitised body if it is enforced for accounts that cannot use encryption is all done in one place
- easy to maintain and expand with future changes (such as the S/MIME pull request on github)
2015-05-25 17:18:39 +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
public function sendEmail ( $email , $keys = array (), $body , $bodyNoEnc = false , $subject , $replyTo = false , $replyToKey = false ) {
// check if the e-mail can be encrypted
$canEncrypt = false ;
if ( isset ( $keys [ 'gpgkey' ]) && ! empty ( $keys [ 'gpgkey' ])) $canEncrypt = true ;
// If bodyonlencrypted is enabled and the user has no encryption key, use the alternate body (if it exists)
if ( Configure :: read ( 'GnuPG.bodyonlyencrypted' ) && ! $canEncrypt && $bodyNoEnc ) {
$body = $bodyNoEnc ;
}
// Sign the body
require_once 'Crypt/GPG.php' ;
try {
$gpg = new Crypt_GPG ( array ( 'homedir' => Configure :: read ( 'GnuPG.homedir' ))); // , 'debug' => true
$gpg -> addSignKey ( Configure :: read ( 'GnuPG.email' ), Configure :: read ( 'GnuPG.password' ));
$body = $gpg -> sign ( $body , Crypt_GPG :: SIGN_MODE_CLEAR );
} catch ( Exception $e ) {
$this -> log ( $e -> getMessage ());
return false ;
}
// If we cannot encrypt the mail and the server settings restricts sending unencrypted messages, return false
if ( ! $canEncrypt && Configure :: read ( 'GnuPG.onlyencrypted' )) return false ;
// Let's encrypt the message if we can
if ( $canEncrypt ) {
$keyImportOutput = $gpg -> importKey ( $keys [ 'gpgkey' ]);
try {
$gpg -> addEncryptKey ( $keyImportOutput [ 'fingerprint' ]); // use the key that was given in the import
$body = $gpg -> encrypt ( $body , true );
} catch ( Exception $e ){
// despite the user having a PGP key and the signing already succeeding earlier, we get an exception. This must mean that there is an issue with the user's key.
$this -> log ( $e -> getMessage ());
return false ;
}
}
$Email = new CakeEmail ();
// 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)
if ( $replyToKey != false && ! empty ( $replyToKey )) $Email -> attachments ( array ( 'gpgkey.asc' => array ( 'data' => $replyToKey )));
$Email -> from ( Configure :: read ( 'MISP.email' ));
$Email -> to ( $email );
$Email -> subject ( $subject );
$Email -> emailFormat ( 'text' );
$response = $Email -> send ( $body );
$Email -> reset ();
debug ( $response [ 'headers' ]);
debug ( $response [ 'message' ]);
}
2013-03-06 11:34:22 +01:00
}