Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
e6672984
Commit
e6672984
authored
Mar 02, 2013
by
Qiang Xue
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Finished cookie.
Finished SecurityHelper.
parent
b858db7d
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
183 additions
and
437 deletions
+183
-437
Application.php
framework/base/Application.php
+0
-8
SecurityManager.php
framework/base/SecurityManager.php
+0
-290
SecurityHelper.php
framework/util/SecurityHelper.php
+87
-65
CookieCollection.php
framework/web/CookieCollection.php
+17
-4
Request.php
framework/web/Request.php
+7
-2
Session.php
framework/web/Session.php
+72
-68
No files found.
framework/base/Application.php
View file @
e6672984
...
@@ -298,14 +298,6 @@ class Application extends Module
...
@@ -298,14 +298,6 @@ class Application extends Module
date_default_timezone_set
(
$value
);
date_default_timezone_set
(
$value
);
}
}
// /**
// * Returns the security manager component.
// * @return SecurityManager the security manager application component.
// */
// public function getSecurityManager()
// {
// return $this->getComponent('securityManager');
// }
//
//
// /**
// /**
// * Returns the locale instance.
// * Returns the locale instance.
...
...
framework/base/SecurityManager.php
deleted
100644 → 0
View file @
b858db7d
<?php
/**
* SecurityManager class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright © 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace
yii\base
;
/**
* SecurityManager provides private keys, hashing and encryption functions.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class
SecurityManager
extends
Component
{
const
STATE_VALIDATION_KEY
=
'Yii.SecurityManager.validationkey'
;
const
STATE_ENCRYPTION_KEY
=
'Yii.SecurityManager.encryptionkey'
;
/**
* @var string the name of the hashing algorithm to be used by {@link computeHMAC}.
* See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible
* hash algorithms. Note that if you are using PHP 5.1.1 or below, you can only use 'sha1' or 'md5'.
*
* Defaults to 'sha1', meaning using SHA1 hash algorithm.
*/
public
$hashAlgorithm
=
'sha1'
;
/**
* @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}.
* This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}.
*
* This property can also be configured as an array. In this case, the array elements will be passed in order
* as parameters to mcrypt_module_open. For example, <code>array('rijndael-256', '', 'ofb', '')</code>.
*
* Defaults to 'des', meaning using DES crypt algorithm.
*/
public
$cryptAlgorithm
=
'des'
;
private
$_validationKey
;
private
$_encryptionKey
;
/**
* @return string a randomly generated private key
*/
protected
function
generateRandomKey
()
{
return
sprintf
(
'%08x%08x%08x%08x'
,
mt_rand
(),
mt_rand
(),
mt_rand
(),
mt_rand
());
}
/**
* @return string the private key used to generate HMAC.
* If the key is not explicitly set, a random one is generated and returned.
*/
public
function
getValidationKey
()
{
if
(
$this
->
_validationKey
!==
null
)
{
return
$this
->
_validationKey
;
}
else
{
if
((
$key
=
\Yii
::
$app
->
getGlobalState
(
self
::
STATE_VALIDATION_KEY
))
!==
null
)
{
$this
->
setValidationKey
(
$key
);
}
else
{
$key
=
$this
->
generateRandomKey
();
$this
->
setValidationKey
(
$key
);
\Yii
::
$app
->
setGlobalState
(
self
::
STATE_VALIDATION_KEY
,
$key
);
}
return
$this
->
_validationKey
;
}
}
/**
* @param string $value the key used to generate HMAC
* @throws CException if the key is empty
*/
public
function
setValidationKey
(
$value
)
{
if
(
!
empty
(
$value
))
{
$this
->
_validationKey
=
$value
;
}
else
{
throw
new
CException
(
Yii
::
t
(
'yii|SecurityManager.validationKey cannot be empty.'
));
}
}
/**
* @return string the private key used to encrypt/decrypt data.
* If the key is not explicitly set, a random one is generated and returned.
*/
public
function
getEncryptionKey
()
{
if
(
$this
->
_encryptionKey
!==
null
)
{
return
$this
->
_encryptionKey
;
}
else
{
if
((
$key
=
\Yii
::
$app
->
getGlobalState
(
self
::
STATE_ENCRYPTION_KEY
))
!==
null
)
{
$this
->
setEncryptionKey
(
$key
);
}
else
{
$key
=
$this
->
generateRandomKey
();
$this
->
setEncryptionKey
(
$key
);
\Yii
::
$app
->
setGlobalState
(
self
::
STATE_ENCRYPTION_KEY
,
$key
);
}
return
$this
->
_encryptionKey
;
}
}
/**
* @param string $value the key used to encrypt/decrypt data.
* @throws CException if the key is empty
*/
public
function
setEncryptionKey
(
$value
)
{
if
(
!
empty
(
$value
))
{
$this
->
_encryptionKey
=
$value
;
}
else
{
throw
new
CException
(
Yii
::
t
(
'yii|SecurityManager.encryptionKey cannot be empty.'
));
}
}
/**
* This method has been deprecated since version 1.1.3.
* Please use {@link hashAlgorithm} instead.
* @return string
*/
public
function
getValidation
()
{
return
$this
->
hashAlgorithm
;
}
/**
* This method has been deprecated since version 1.1.3.
* Please use {@link hashAlgorithm} instead.
* @param string $value -
*/
public
function
setValidation
(
$value
)
{
$this
->
hashAlgorithm
=
$value
;
}
/**
* Encrypts data.
* @param string $data data to be encrypted.
* @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
* @return string the encrypted data
* @throws CException if PHP Mcrypt extension is not loaded
*/
public
function
encrypt
(
$data
,
$key
=
null
)
{
$module
=
$this
->
openCryptModule
();
$key
=
$this
->
substr
(
$key
===
null
?
md5
(
$this
->
getEncryptionKey
())
:
$key
,
0
,
mcrypt_enc_get_key_size
(
$module
));
srand
();
$iv
=
mcrypt_create_iv
(
mcrypt_enc_get_iv_size
(
$module
),
MCRYPT_RAND
);
mcrypt_generic_init
(
$module
,
$key
,
$iv
);
$encrypted
=
$iv
.
mcrypt_generic
(
$module
,
$data
);
mcrypt_generic_deinit
(
$module
);
mcrypt_module_close
(
$module
);
return
$encrypted
;
}
/**
* Decrypts data
* @param string $data data to be decrypted.
* @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
* @return string the decrypted data
* @throws CException if PHP Mcrypt extension is not loaded
*/
public
function
decrypt
(
$data
,
$key
=
null
)
{
$module
=
$this
->
openCryptModule
();
$key
=
$this
->
substr
(
$key
===
null
?
md5
(
$this
->
getEncryptionKey
())
:
$key
,
0
,
mcrypt_enc_get_key_size
(
$module
));
$ivSize
=
mcrypt_enc_get_iv_size
(
$module
);
$iv
=
$this
->
substr
(
$data
,
0
,
$ivSize
);
mcrypt_generic_init
(
$module
,
$key
,
$iv
);
$decrypted
=
mdecrypt_generic
(
$module
,
$this
->
substr
(
$data
,
$ivSize
,
$this
->
strlen
(
$data
)));
mcrypt_generic_deinit
(
$module
);
mcrypt_module_close
(
$module
);
return
rtrim
(
$decrypted
,
"
\0
"
);
}
/**
* Opens the mcrypt module with the configuration specified in {@link cryptAlgorithm}.
* @return resource the mycrypt module handle.
* @since 1.1.3
*/
protected
function
openCryptModule
()
{
if
(
extension_loaded
(
'mcrypt'
))
{
if
(
is_array
(
$this
->
cryptAlgorithm
))
{
$module
=
@
call_user_func_array
(
'mcrypt_module_open'
,
$this
->
cryptAlgorithm
);
}
else
{
$module
=
@
mcrypt_module_open
(
$this
->
cryptAlgorithm
,
''
,
MCRYPT_MODE_CBC
,
''
);
}
if
(
$module
===
false
)
{
throw
new
CException
(
Yii
::
t
(
'yii|Failed to initialize the mcrypt module.'
));
}
return
$module
;
}
else
{
throw
new
CException
(
Yii
::
t
(
'yii|SecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'
));
}
}
/**
* Prefixes data with an HMAC.
* @param string $data data to be hashed.
* @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
* @return string data prefixed with HMAC
*/
public
function
hashData
(
$data
,
$key
=
null
)
{
return
$this
->
computeHMAC
(
$data
,
$key
)
.
$data
;
}
/**
* Validates if data is tampered.
* @param string $data data to be validated. The data must be previously
* generated using {@link hashData()}.
* @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
* @return string the real data with HMAC stripped off. False if the data
* is tampered.
*/
public
function
validateData
(
$data
,
$key
=
null
)
{
$len
=
$this
->
strlen
(
$this
->
computeHMAC
(
'test'
));
if
(
$this
->
strlen
(
$data
)
>=
$len
)
{
$hmac
=
$this
->
substr
(
$data
,
0
,
$len
);
$data2
=
$this
->
substr
(
$data
,
$len
,
$this
->
strlen
(
$data
));
return
$hmac
===
$this
->
computeHMAC
(
$data2
,
$key
)
?
$data2
:
false
;
}
else
{
return
false
;
}
}
/**
* Computes the HMAC for the data with {@link getValidationKey ValidationKey}.
* @param string $data data to be generated HMAC
* @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
* @return string the HMAC for the data
*/
protected
function
computeHMAC
(
$data
,
$key
=
null
)
{
if
(
$key
===
null
)
{
$key
=
$this
->
getValidationKey
();
}
if
(
function_exists
(
'hash_hmac'
))
{
return
hash_hmac
(
$this
->
hashAlgorithm
,
$data
,
$key
);
}
if
(
!
strcasecmp
(
$this
->
hashAlgorithm
,
'sha1'
))
{
$pack
=
'H40'
;
$func
=
'sha1'
;
}
else
{
$pack
=
'H32'
;
$func
=
'md5'
;
}
if
(
$this
->
strlen
(
$key
)
>
64
)
{
$key
=
pack
(
$pack
,
$func
(
$key
));
}
if
(
$this
->
strlen
(
$key
)
<
64
)
{
$key
=
str_pad
(
$key
,
64
,
chr
(
0
));
}
$key
=
$this
->
substr
(
$key
,
0
,
64
);
return
$func
((
str_repeat
(
chr
(
0x5C
),
64
)
^
$key
)
.
pack
(
$pack
,
$func
((
str_repeat
(
chr
(
0x36
),
64
)
^
$key
)
.
$data
)));
}
/**
* Returns the length of the given string.
* If available uses the multibyte string function mb_strlen.
* @param string $string the string being measured for length
* @return int the length of the string
*/
private
function
strlen
(
$string
)
{
return
function_exists
(
'mb_strlen'
)
?
mb_strlen
(
$string
,
'8bit'
)
:
strlen
(
$string
);
}
/**
* Returns the portion of string specified by the start and length parameters.
* If available uses the multibyte string function mb_substr
* @param string $string the input string. Must be one character or longer.
* @param int $start the starting position
* @param int $length the desired portion length
* @return string the extracted part of string, or FALSE on failure or an empty string.
*/
private
function
substr
(
$string
,
$start
,
$length
)
{
return
function_exists
(
'mb_substr'
)
?
mb_substr
(
$string
,
$start
,
$length
,
'8bit'
)
:
substr
(
$string
,
$start
,
$length
);
}
}
framework/util/
Password
Helper.php
→
framework/util/
Security
Helper.php
View file @
e6672984
<?php
<?php
/**
/**
*
Password
Helper class file.
*
Security
Helper class file.
*
*
* @link http://www.yiiframework.com/
* @link http://www.yiiframework.com/
* @copyright Copyright © 2008 Yii Software LLC
* @copyright Copyright © 2008 Yii Software LLC
...
@@ -9,65 +9,37 @@
...
@@ -9,65 +9,37 @@
namespace
yii\util
;
namespace
yii\util
;
use
Yii
;
use
yii\base\Exception
;
use
yii\base\Exception
;
use
yii\base\InvalidConfigException
;
use
yii\base\InvalidConfigException
;
use
yii\base\InvalidParamException
;
use
yii\base\InvalidParamException
;
/**
/**
*
PasswordHelper provides a simple API for secure password hashing and verification
.
*
SecurityHelper provides a set of methods to handle common security-related tasks
.
*
*
* PasswordHelper uses the Blowfish hash algorithm available in many PHP runtime
* In particular, SecurityHelper supports the following features:
* environments through the PHP [crypt()](http://php.net/manual/en/function.crypt.php)
* built-in function. As of Dec 2012 it is the strongest algorithm available in PHP
* and the only algorithm without some security concerns surrounding it. For this reason,
* PasswordHelper fails to initialize when run in and environment that does not have
* crypt() and its Blowfish option. Systems with the option include:
*
*
*
1. Most *nix systems since PHP 4 (the algorithm is part of the library function crypt(3));
*
- Encryption/decryption: [[encrypt()]] and [[decrypt()]]
*
2. All PHP systems since 5.3.0;
*
- Data tampering prevention: [[hashData()]] and [[validateData()]]
*
3. All PHP systems with the [Suhosin patch](http://www.hardened-php.net/suhosin/).
*
- Password validation: [[generatePasswordHash()]] and [[validatePassword()]]
*
*
* For more information about password hashing, crypt() and Blowfish, please read
* Additionally, SecurityHelper provides [[getSecretKey()]] to support generating
* the Yii Wiki article [Use crypt() for password storage](http://www.yiiframework.com/wiki/425/use-crypt-for-password-storage/)
* named secret keys. These secret keys, once generated, will be stored in a file
* and the PHP RFC [Adding simple password hashing API](http://wiki.php.net/rfc/password_hash).
* and made available in future requests.
*
* PasswordHelper throws an exception if the Blowfish hash algorithm is not
* available in the runtime PHP's crypt() function. It can be used as follows
*
* Generate a hash from a password:
*
* ~~~
* $hash = PasswordHelper::hashPassword($password);
* ~~~
*
* This hash can be stored in a database (e.g. `CHAR(64) CHARACTER SET latin1` on MySQL). The
* hash is usually generated and saved to the database when the user enters a new password.
* But it can also be useful to generate and save a hash after validating a user's
* password in order to change the cost or refresh the salt.
*
* To verify a password, fetch the user's saved hash from the database (into `$hash`) and:
*
* ~~~
* if (PasswordHelper::verifyPassword($password, $hash) {
* // password is good
* } else {
* // password is bad
* }
* ~~~
*
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Tom Worster <fsb@thefsb.org>
* @author Tom Worster <fsb@thefsb.org>
* @since 2.0
* @since 2.0
*/
*/
class
SecurityHelper
class
PasswordHelper
{
{
/**
/**
* Encrypts data.
* Encrypts data.
* @param string $data data to be encrypted.
* @param string $data data to be encrypted.
* @param string $key the encryption secret key
* @param string $key the encryption secret key
* @return string the encrypted data
* @return string the encrypted data
* @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
* @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
* @see decrypt()
*/
*/
public
static
function
encrypt
(
$data
,
$key
)
public
static
function
encrypt
(
$data
,
$key
)
{
{
...
@@ -88,6 +60,7 @@ class PasswordHelper
...
@@ -88,6 +60,7 @@ class PasswordHelper
* @param string $key the decryption secret key
* @param string $key the decryption secret key
* @return string the decrypted data
* @return string the decrypted data
* @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
* @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
* @see encrypt()
*/
*/
public
static
function
decrypt
(
$data
,
$key
)
public
static
function
decrypt
(
$data
,
$key
)
{
{
...
@@ -103,37 +76,70 @@ class PasswordHelper
...
@@ -103,37 +76,70 @@ class PasswordHelper
}
}
/**
/**
* Prefixes data with an HMAC.
* Prefixes data with a keyed hash value so that it can later be detected if it is tampered.
* @param string $data data to be hashed.
* @param string $data the data to be protected
* @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
* @param string $key the secret key to be used for generating hash
* @return string data prefixed with HMAC
* @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()"
* function to see the supported hashing algorithms on your system.
* @return string the data prefixed with the keyed hash
* @see validateData()
* @see getSecretKey()
*/
*/
public
static
function
hashData
(
$data
,
$key
)
public
static
function
hashData
(
$data
,
$key
,
$algorithm
=
'sha256'
)
{
{
return
hash_hmac
(
'sha1'
,
$data
,
$key
)
.
$data
;
return
hash_hmac
(
$algorithm
,
$data
,
$key
)
.
$data
;
}
}
/**
/**
* Validates if data is tampered.
* Validates if the given data is tampered.
* @param string $data data to be validated. The data must be previously
* @param string $data the data to be validated. The data must be previously
* generated using {@link hashData()}.
* generated by [[hashData()]].
* @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
* @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]].
* @return string the real data with HMAC stripped off. False if the data
* @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()"
* is tampered.
* function to see the supported hashing algorithms on your system. This must be the same
* as the value passed to [[hashData()]] when generating the hash for the data.
* @return string the real data with the hash stripped off. False if the data is tampered.
* @see hashData()
*/
*/
public
function
validateData
(
$data
,
$key
=
null
)
public
static
function
validateData
(
$data
,
$key
,
$algorithm
=
'sha256'
)
{
{
$len
=
$this
->
strlen
(
$this
->
computeHMAC
(
'test'
));
$hashSize
=
StringHelper
::
strlen
(
hash_hmac
(
$algorithm
,
'test'
,
$key
));
if
(
$this
->
strlen
(
$data
)
>=
$len
)
{
$n
=
StringHelper
::
strlen
(
$data
);
$hmac
=
$this
->
substr
(
$data
,
0
,
$len
);
if
(
$n
>=
$hashSize
)
{
$data2
=
$this
->
substr
(
$data
,
$len
,
$this
->
strlen
(
$data
));
$hash
=
StringHelper
::
substr
(
$data
,
0
,
$hashSize
);
return
$hmac
===
$this
->
computeHMAC
(
$data2
,
$key
)
?
$data2
:
false
;
$data2
=
StringHelper
::
substr
(
$data
,
$hashSize
,
$n
-
$hashSize
);
return
$hash
===
hash_hmac
(
$algorithm
,
$data2
,
$key
)
?
$data2
:
false
;
}
else
{
}
else
{
return
false
;
return
false
;
}
}
}
}
/**
/**
* Returns a secret key associated with the specified name.
* If the secret key does not exist, a random key will be generated
* and saved in the file "keys.php" under the application's runtime directory
* so that the same secret key can be returned in future requests.
* @param string $name the name that is associated with the secret key
* @param integer $length the length of the key that should be generated if not exists
* @return string the secret key associated with the specified name
*/
public
static
function
getSecretKey
(
$name
,
$length
=
32
)
{
static
$keys
;
$keyFile
=
Yii
::
$app
->
getRuntimePath
()
.
'/keys.php'
;
if
(
$keys
===
null
)
{
$keys
=
is_file
(
$keyFile
)
?
require
(
$keyFile
)
:
array
();
}
if
(
!
isset
(
$keys
[
$name
]))
{
// generate a 32-char random key
$chars
=
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
;
$keys
[
$name
]
=
substr
(
str_shuffle
(
str_repeat
(
$chars
,
5
)),
0
,
$length
);
file_put_contents
(
$keyFile
,
"<?php
\n
return "
.
var_export
(
$keys
,
true
)
.
";
\n
"
);
}
return
$keys
[
$name
];
}
/**
* Opens the mcrypt module.
* Opens the mcrypt module.
* @return resource the mcrypt module handle.
* @return resource the mcrypt module handle.
* @throws InvalidConfigException if mcrypt extension is not installed
* @throws InvalidConfigException if mcrypt extension is not installed
...
@@ -144,7 +150,7 @@ class PasswordHelper
...
@@ -144,7 +150,7 @@ class PasswordHelper
if
(
!
extension_loaded
(
'mcrypt'
))
{
if
(
!
extension_loaded
(
'mcrypt'
))
{
throw
new
InvalidConfigException
(
'The mcrypt PHP extension is not installed.'
);
throw
new
InvalidConfigException
(
'The mcrypt PHP extension is not installed.'
);
}
}
$module
=
@
mcrypt_module_open
(
'
des
'
,
''
,
MCRYPT_MODE_CBC
,
''
);
$module
=
@
mcrypt_module_open
(
'
rijndael-256
'
,
''
,
MCRYPT_MODE_CBC
,
''
);
if
(
$module
===
false
)
{
if
(
$module
===
false
)
{
throw
new
Exception
(
'Failed to initialize the mcrypt module.'
);
throw
new
Exception
(
'Failed to initialize the mcrypt module.'
);
}
}
...
@@ -152,10 +158,24 @@ class PasswordHelper
...
@@ -152,10 +158,24 @@ class PasswordHelper
}
}
/**
/**
* Generate a secure hash from a password and a random salt.
* Generates a secure hash from a password and a random salt.
*
* The generated hash can be stored in database (e.g. `CHAR(64) CHARACTER SET latin1` on MySQL).
* Later when a password needs to be validated, the hash can be fetched and passed
* to [[validatePassword()]]. For example,
*
*
* Uses the PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function
* ~~~
* with the Blowfish hash option.
* // generates the hash (usually done during user registration or when the password is changed)
* $hash = SecurityHelper::hashPassword($password);
* // ...save $hash in database...
*
* // during login, validate if the password entered is correct using $hash fetched from database
* if (PasswordHelper::verifyPassword($password, $hash) {
* // password is good
* } else {
* // password is bad
* }
* ~~~
*
*
* @param string $password The password to be hashed.
* @param string $password The password to be hashed.
* @param integer $cost Cost parameter used by the Blowfish hash algorithm.
* @param integer $cost Cost parameter used by the Blowfish hash algorithm.
...
@@ -168,8 +188,9 @@ class PasswordHelper
...
@@ -168,8 +188,9 @@ class PasswordHelper
* 2^($cost - 14) seconds.
* 2^($cost - 14) seconds.
* @throws Exception on bad password parameter or cost parameter
* @throws Exception on bad password parameter or cost parameter
* @return string The password hash string, ASCII and not longer than 64 characters.
* @return string The password hash string, ASCII and not longer than 64 characters.
* @see validatePassword()
*/
*/
public
static
function
hashPassword
(
$password
,
$cost
=
13
)
public
static
function
generatePasswordHash
(
$password
,
$cost
=
13
)
{
{
$salt
=
static
::
generateSalt
(
$cost
);
$salt
=
static
::
generateSalt
(
$cost
);
$hash
=
crypt
(
$password
,
$salt
);
$hash
=
crypt
(
$password
,
$salt
);
...
@@ -187,8 +208,9 @@ class PasswordHelper
...
@@ -187,8 +208,9 @@ class PasswordHelper
* @param string $hash The hash to verify the password against.
* @param string $hash The hash to verify the password against.
* @return boolean whether the password is correct.
* @return boolean whether the password is correct.
* @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available.
* @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available.
* @see generatePasswordHash()
*/
*/
public
static
function
v
erify
Password
(
$password
,
$hash
)
public
static
function
v
alidate
Password
(
$password
,
$hash
)
{
{
if
(
!
is_string
(
$password
)
||
$password
===
''
)
{
if
(
!
is_string
(
$password
)
||
$password
===
''
)
{
throw
new
InvalidParamException
(
'Password must be a string and cannot be empty.'
);
throw
new
InvalidParamException
(
'Password must be a string and cannot be empty.'
);
...
...
framework/web/CookieCollection.php
View file @
e6672984
...
@@ -11,6 +11,7 @@ namespace yii\web;
...
@@ -11,6 +11,7 @@ namespace yii\web;
use
Yii
;
use
Yii
;
use
yii\base\DictionaryIterator
;
use
yii\base\DictionaryIterator
;
use
yii\util\SecurityHelper
;
/**
/**
* CookieCollection maintains the cookies available in the current request.
* CookieCollection maintains the cookies available in the current request.
...
@@ -27,6 +28,10 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
...
@@ -27,6 +28,10 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
* if a cookie is tampered on the client side, it will be ignored when received on the server side.
* if a cookie is tampered on the client side, it will be ignored when received on the server side.
*/
*/
public
$enableValidation
=
true
;
public
$enableValidation
=
true
;
/**
* @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
*/
public
$validationKey
;
/**
/**
* @var Cookie[] the cookies in this collection (indexed by the cookie names)
* @var Cookie[] the cookies in this collection (indexed by the cookie names)
...
@@ -111,7 +116,12 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
...
@@ -111,7 +116,12 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
$value
=
$cookie
->
value
;
$value
=
$cookie
->
value
;
if
(
$this
->
enableValidation
)
{
if
(
$this
->
enableValidation
)
{
$value
=
Yii
::
$app
->
getSecurityManager
()
->
hashData
(
serialize
(
$value
));
if
(
$this
->
validationKey
===
null
)
{
$key
=
SecurityHelper
::
getSecretKey
(
__CLASS__
.
'/'
.
Yii
::
$app
->
id
);
}
else
{
$key
=
$this
->
validationKey
;
}
$value
=
SecurityHelper
::
hashData
(
serialize
(
$value
),
$key
);
}
}
setcookie
(
$cookie
->
name
,
$value
,
$cookie
->
expire
,
$cookie
->
path
,
$cookie
->
domain
,
$cookie
->
secure
,
$cookie
->
httpOnly
);
setcookie
(
$cookie
->
name
,
$value
,
$cookie
->
expire
,
$cookie
->
path
,
$cookie
->
domain
,
$cookie
->
secure
,
$cookie
->
httpOnly
);
...
@@ -205,7 +215,6 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
...
@@ -205,7 +215,6 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
$this
->
remove
(
$name
);
$this
->
remove
(
$name
);
}
}
/**
/**
* Returns the current cookies in terms of [[Cookie]] objects.
* Returns the current cookies in terms of [[Cookie]] objects.
* @return Cookie[] list of current cookies
* @return Cookie[] list of current cookies
...
@@ -214,9 +223,13 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
...
@@ -214,9 +223,13 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
{
{
$cookies
=
array
();
$cookies
=
array
();
if
(
$this
->
enableValidation
)
{
if
(
$this
->
enableValidation
)
{
$sm
=
\Yii
::
$app
->
getSecurityManager
();
if
(
$this
->
validationKey
===
null
)
{
$key
=
SecurityHelper
::
getSecretKey
(
__CLASS__
.
'/'
.
Yii
::
$app
->
id
);
}
else
{
$key
=
$this
->
validationKey
;
}
foreach
(
$_COOKIE
as
$name
=>
$value
)
{
foreach
(
$_COOKIE
as
$name
=>
$value
)
{
if
(
is_string
(
$value
)
&&
(
$value
=
$sm
->
validateData
(
$value
))
!==
false
)
{
if
(
is_string
(
$value
)
&&
(
$value
=
SecurityHelper
::
validateData
(
$value
,
$key
))
!==
false
)
{
$cookies
[
$name
]
=
new
Cookie
(
array
(
$cookies
[
$name
]
=
new
Cookie
(
array
(
'name'
=>
$name
,
'name'
=>
$name
,
'value'
=>
@
unserialize
(
$value
),
'value'
=>
@
unserialize
(
$value
),
...
...
framework/web/Request.php
View file @
e6672984
...
@@ -19,9 +19,13 @@ use yii\base\InvalidConfigException;
...
@@ -19,9 +19,13 @@ use yii\base\InvalidConfigException;
class
Request
extends
\yii\base\Request
class
Request
extends
\yii\base\Request
{
{
/**
/**
* @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to
fals
e.
* @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to
tru
e.
*/
*/
public
$enableCookieValidation
=
false
;
public
$enableCookieValidation
=
true
;
/**
* @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
*/
public
$cookieValidationKey
;
/**
/**
* @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false.
* @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false.
* By setting this property to true, forms submitted to an Yii Web application must be originated
* By setting this property to true, forms submitted to an Yii Web application must be originated
...
@@ -721,6 +725,7 @@ class Request extends \yii\base\Request
...
@@ -721,6 +725,7 @@ class Request extends \yii\base\Request
if
(
$this
->
_cookies
===
null
)
{
if
(
$this
->
_cookies
===
null
)
{
$this
->
_cookies
=
new
CookieCollection
(
array
(
$this
->
_cookies
=
new
CookieCollection
(
array
(
'enableValidation'
=>
$this
->
enableCookieValidation
,
'enableValidation'
=>
$this
->
enableCookieValidation
,
'validationKey'
=>
$this
->
cookieValidationKey
,
));
));
}
}
return
$this
->
_cookies
;
return
$this
->
_cookies
;
...
...
framework/web/Session.php
View file @
e6672984
...
@@ -70,12 +70,12 @@
...
@@ -70,12 +70,12 @@
* @package system.web
* @package system.web
* @since 1.0
* @since 1.0
*/
*/
class
CHttpSession
extends
CApplicationComponent
implements
IteratorAggregate
,
ArrayAccess
,
Countable
class
CHttpSession
extends
CApplicationComponent
implements
IteratorAggregate
,
ArrayAccess
,
Countable
{
{
/**
/**
* @var boolean whether the session should be automatically started when the session application component is initialized, defaults to true.
* @var boolean whether the session should be automatically started when the session application component is initialized, defaults to true.
*/
*/
public
$autoStart
=
true
;
public
$autoStart
=
true
;
/**
/**
...
@@ -85,9 +85,10 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -85,9 +85,10 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
public
function
init
()
public
function
init
()
{
{
parent
::
init
();
parent
::
init
();
if
(
$this
->
autoStart
)
if
(
$this
->
autoStart
)
{
$this
->
open
();
$this
->
open
();
register_shutdown_function
(
array
(
$this
,
'close'
));
}
register_shutdown_function
(
array
(
$this
,
'close'
));
}
}
/**
/**
...
@@ -109,18 +110,18 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -109,18 +110,18 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
open
()
public
function
open
()
{
{
if
(
$this
->
getUseCustomStorage
())
if
(
$this
->
getUseCustomStorage
())
{
@
session_set_save_handler
(
array
(
$this
,
'openSession'
),
array
(
$this
,
'closeSession'
),
array
(
$this
,
'readSession'
),
array
(
$this
,
'writeSession'
),
array
(
$this
,
'destroySession'
),
array
(
$this
,
'gcSession'
));
@
session_set_save_handler
(
array
(
$this
,
'openSession'
),
array
(
$this
,
'closeSession'
),
array
(
$this
,
'readSession'
),
array
(
$this
,
'writeSession'
),
array
(
$this
,
'destroySession'
),
array
(
$this
,
'gcSession'
));
}
@
session_start
();
@
session_start
();
if
(
YII_DEBUG
&&
session_id
()
==
''
)
if
(
YII_DEBUG
&&
session_id
()
==
''
)
{
{
$message
=
Yii
::
t
(
'yii|Failed to start session.'
);
$message
=
Yii
::
t
(
'yii|Failed to start session.'
);
if
(
function_exists
(
'error_get_last'
))
{
if
(
function_exists
(
'error_get_last'
))
$error
=
error_get_last
();
{
if
(
isset
(
$error
[
'message'
]))
{
$error
=
error_get_last
();
$message
=
$error
[
'message'
];
if
(
isset
(
$error
[
'message'
]))
}
$message
=
$error
[
'message'
];
}
}
Yii
::
log
(
$message
,
CLogger
::
LEVEL_WARNING
,
'system.web.CHttpSession'
);
Yii
::
log
(
$message
,
CLogger
::
LEVEL_WARNING
,
'system.web.CHttpSession'
);
}
}
...
@@ -131,17 +132,17 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -131,17 +132,17 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
close
()
public
function
close
()
{
{
if
(
session_id
()
!==
''
)
if
(
session_id
()
!==
''
)
{
@
session_write_close
();
@
session_write_close
();
}
}
}
/**
/**
* Frees all session variables and destroys all data registered to a session.
* Frees all session variables and destroys all data registered to a session.
*/
*/
public
function
destroy
()
public
function
destroy
()
{
{
if
(
session_id
()
!==
''
)
if
(
session_id
()
!==
''
)
{
{
@
session_unset
();
@
session_unset
();
@
session_destroy
();
@
session_destroy
();
}
}
...
@@ -152,7 +153,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -152,7 +153,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
getIsStarted
()
public
function
getIsStarted
()
{
{
return
session_id
()
!==
''
;
return
session_id
()
!==
''
;
}
}
/**
/**
...
@@ -177,7 +178,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -177,7 +178,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
* @param boolean $deleteOldSession Whether to delete the old associated session file or not.
* @param boolean $deleteOldSession Whether to delete the old associated session file or not.
* @since 1.1.8
* @since 1.1.8
*/
*/
public
function
regenerateID
(
$deleteOldSession
=
false
)
public
function
regenerateID
(
$deleteOldSession
=
false
)
{
{
session_regenerate_id
(
$deleteOldSession
);
session_regenerate_id
(
$deleteOldSession
);
}
}
...
@@ -212,11 +213,12 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -212,11 +213,12 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
setSavePath
(
$value
)
public
function
setSavePath
(
$value
)
{
{
if
(
is_dir
(
$value
))
if
(
is_dir
(
$value
))
{
session_save_path
(
$value
);
session_save_path
(
$value
);
else
}
else
{
throw
new
CException
(
Yii
::
t
(
'yii|CHttpSession.savePath "{path}" is not a valid directory.'
,
throw
new
CException
(
Yii
::
t
(
'yii|CHttpSession.savePath "{path}" is not a valid directory.'
,
array
(
'{path}'
=>
$value
)));
array
(
'{path}'
=>
$value
)));
}
}
}
/**
/**
...
@@ -237,13 +239,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -237,13 +239,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
setCookieParams
(
$value
)
public
function
setCookieParams
(
$value
)
{
{
$data
=
session_get_cookie_params
();
$data
=
session_get_cookie_params
();
extract
(
$data
);
extract
(
$data
);
extract
(
$value
);
extract
(
$value
);
if
(
isset
(
$httponly
))
if
(
isset
(
$httponly
))
{
session_set_cookie_params
(
$lifetime
,
$path
,
$domain
,
$secure
,
$httponly
);
session_set_cookie_params
(
$lifetime
,
$path
,
$domain
,
$secure
,
$httponly
);
else
}
else
{
session_set_cookie_params
(
$lifetime
,
$path
,
$domain
,
$secure
);
session_set_cookie_params
(
$lifetime
,
$path
,
$domain
,
$secure
);
}
}
}
/**
/**
...
@@ -251,36 +254,38 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -251,36 +254,38 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
getCookieMode
()
public
function
getCookieMode
()
{
{
if
(
ini_get
(
'session.use_cookies'
)
===
'0'
)
if
(
ini_get
(
'session.use_cookies'
)
===
'0'
)
{
return
'none'
;
return
'none'
;
else
if
(
ini_get
(
'session.use_only_cookies'
)
===
'0'
)
}
else
{
if
(
ini_get
(
'session.use_only_cookies'
)
===
'0'
)
{
return
'allow'
;
return
'allow'
;
else
}
else
{
return
'only'
;
return
'only'
;
}
}
}
}
/**
/**
* @param string $value how to use cookie to store session ID. Valid values include 'none', 'allow' and 'only'.
* @param string $value how to use cookie to store session ID. Valid values include 'none', 'allow' and 'only'.
*/
*/
public
function
setCookieMode
(
$value
)
public
function
setCookieMode
(
$value
)
{
{
if
(
$value
===
'none'
)
if
(
$value
===
'none'
)
{
{
ini_set
(
'session.use_cookies'
,
'0'
);
ini_set
(
'session.use_cookies'
,
'0'
);
ini_set
(
'session.use_only_cookies'
,
'0'
);
ini_set
(
'session.use_only_cookies'
,
'0'
);
}
else
{
if
(
$value
===
'allow'
)
{
ini_set
(
'session.use_cookies'
,
'1'
);
ini_set
(
'session.use_only_cookies'
,
'0'
);
}
else
{
if
(
$value
===
'only'
)
{
ini_set
(
'session.use_cookies'
,
'1'
);
ini_set
(
'session.use_only_cookies'
,
'1'
);
}
else
{
throw
new
CException
(
Yii
::
t
(
'yii|CHttpSession.cookieMode can only be "none", "allow" or "only".'
));
}
}
else
if
(
$value
===
'allow'
)
{
ini_set
(
'session.use_cookies'
,
'1'
);
ini_set
(
'session.use_only_cookies'
,
'0'
);
}
}
else
if
(
$value
===
'only'
)
{
ini_set
(
'session.use_cookies'
,
'1'
);
ini_set
(
'session.use_only_cookies'
,
'1'
);
}
}
else
throw
new
CException
(
Yii
::
t
(
'yii|CHttpSession.cookieMode can only be "none", "allow" or "only".'
));
}
}
/**
/**
...
@@ -297,15 +302,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -297,15 +302,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
setGCProbability
(
$value
)
public
function
setGCProbability
(
$value
)
{
{
$value
=
(
int
)
$value
;
$value
=
(
int
)
$value
;
if
(
$value
>=
0
&&
$value
<=
100
)
if
(
$value
>=
0
&&
$value
<=
100
)
{
{
ini_set
(
'session.gc_probability'
,
$value
);
ini_set
(
'session.gc_probability'
,
$value
);
ini_set
(
'session.gc_divisor'
,
'100'
);
ini_set
(
'session.gc_divisor'
,
'100'
);
}
else
{
}
else
throw
new
CException
(
Yii
::
t
(
'yii|CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.'
,
throw
new
CException
(
Yii
::
t
(
'yii|CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.'
,
array
(
'{value}'
=>
$value
)));
array
(
'{value}'
=>
$value
)));
}
}
}
/**
/**
...
@@ -313,7 +317,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -313,7 +317,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
getUseTransparentSessionID
()
public
function
getUseTransparentSessionID
()
{
{
return
ini_get
(
'session.use_trans_sid'
)
==
1
;
return
ini_get
(
'session.use_trans_sid'
)
==
1
;
}
}
/**
/**
...
@@ -321,7 +325,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -321,7 +325,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
setUseTransparentSessionID
(
$value
)
public
function
setUseTransparentSessionID
(
$value
)
{
{
ini_set
(
'session.use_trans_sid'
,
$value
?
'1'
:
'0'
);
ini_set
(
'session.use_trans_sid'
,
$value
?
'1'
:
'0'
);
}
}
/**
/**
...
@@ -337,7 +341,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -337,7 +341,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
setTimeout
(
$value
)
public
function
setTimeout
(
$value
)
{
{
ini_set
(
'session.gc_maxlifetime'
,
$value
);
ini_set
(
'session.gc_maxlifetime'
,
$value
);
}
}
/**
/**
...
@@ -348,7 +352,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -348,7 +352,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
* @param string $sessionName session name
* @param string $sessionName session name
* @return boolean whether session is opened successfully
* @return boolean whether session is opened successfully
*/
*/
public
function
openSession
(
$savePath
,
$sessionName
)
public
function
openSession
(
$savePath
,
$sessionName
)
{
{
return
true
;
return
true
;
}
}
...
@@ -384,7 +388,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -384,7 +388,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
* @param string $data session data
* @param string $data session data
* @return boolean whether session write is successful
* @return boolean whether session write is successful
*/
*/
public
function
writeSession
(
$id
,
$data
)
public
function
writeSession
(
$id
,
$data
)
{
{
return
true
;
return
true
;
}
}
...
@@ -461,7 +465,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -461,7 +465,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
* @return mixed the session variable value, or $defaultValue if the session variable does not exist.
* @return mixed the session variable value, or $defaultValue if the session variable does not exist.
* @since 1.1.2
* @since 1.1.2
*/
*/
public
function
get
(
$key
,
$defaultValue
=
null
)
public
function
get
(
$key
,
$defaultValue
=
null
)
{
{
return
isset
(
$_SESSION
[
$key
])
?
$_SESSION
[
$key
]
:
$defaultValue
;
return
isset
(
$_SESSION
[
$key
])
?
$_SESSION
[
$key
]
:
$defaultValue
;
}
}
...
@@ -483,9 +487,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -483,9 +487,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
* @param mixed $key session variable name
* @param mixed $key session variable name
* @param mixed $value session variable value
* @param mixed $value session variable value
*/
*/
public
function
add
(
$key
,
$value
)
public
function
add
(
$key
,
$value
)
{
{
$_SESSION
[
$key
]
=
$value
;
$_SESSION
[
$key
]
=
$value
;
}
}
/**
/**
...
@@ -495,24 +499,24 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -495,24 +499,24 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
*/
*/
public
function
remove
(
$key
)
public
function
remove
(
$key
)
{
{
if
(
isset
(
$_SESSION
[
$key
]))
if
(
isset
(
$_SESSION
[
$key
]))
{
{
$value
=
$_SESSION
[
$key
];
$value
=
$_SESSION
[
$key
];
unset
(
$_SESSION
[
$key
]);
unset
(
$_SESSION
[
$key
]);
return
$value
;
return
$value
;
}
}
else
{
else
return
null
;
return
null
;
}
}
}
/**
/**
* Removes all session variables
* Removes all session variables
*/
*/
public
function
clear
()
public
function
clear
()
{
{
foreach
(
array_keys
(
$_SESSION
)
as
$key
)
foreach
(
array_keys
(
$_SESSION
)
as
$key
)
{
unset
(
$_SESSION
[
$key
]);
unset
(
$_SESSION
[
$key
]);
}
}
}
/**
/**
* @param mixed $key session variable name
* @param mixed $key session variable name
...
@@ -556,9 +560,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
...
@@ -556,9 +560,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
* @param integer $offset the offset to set element
* @param integer $offset the offset to set element
* @param mixed $item the element value
* @param mixed $item the element value
*/
*/
public
function
offsetSet
(
$offset
,
$item
)
public
function
offsetSet
(
$offset
,
$item
)
{
{
$_SESSION
[
$offset
]
=
$item
;
$_SESSION
[
$offset
]
=
$item
;
}
}
/**
/**
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment