Merge branch 'next' into feature/ckeditor

This commit is contained in:
bnu 2015-02-24 20:48:20 +09:00
commit 95154d6985
18 changed files with 795 additions and 112 deletions

View file

@ -15,37 +15,7 @@ RewriteRule ^(.+)/files/(member_extra_info|attach|cache|faceOff)/(.*) ./files/$2
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.+)/(files|modules|widgets|widgetstyles|layouts|m.layouts|addons)/(.*) ./$2/$3 [L]
# rss , blogAPI
RewriteRule ^(rss|atom)$ ./index.php?module=rss&act=$1 [L]
RewriteRule ^([a-zA-Z0-9_]+)/(rss|atom|api)$ ./index.php?mid=$1&act=$2 [L]
RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/(rss|atom|api)$ ./index.php?vid=$1&mid=$2&act=$3 [L]
# trackback
RewriteRule ^([0-9]+)/(.+)/trackback$ ./index.php?document_srl=$1&key=$2&act=trackback [L]
RewriteRule ^([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback$ ./index.php?mid=$1&document_srl=$2&key=$3&act=trackback [L]
RewriteRule ^([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback$ ./index.php?vid=$1&document_srl=$2&key=$3&act=trackback [L]
RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback$ ./index.php?vid=$1&mid=$2&document_srl=$3&key=$4&act=trackback [L]
# document permanent link
RewriteRule ^([0-9]+)$ ./index.php?document_srl=$1 [L,QSA]
# mid link
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^([a-zA-Z0-9_]+)/?$ ./index.php?mid=$1 [L,QSA]
# mid + document link
RewriteRule ^([a-zA-Z0-9_]+)/([0-9]+)$ ./index.php?mid=$1&document_srl=$2 [L,QSA]
# vid + mid link
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/?$ ./index.php?vid=$1&mid=$2 [L,QSA]
# vid + mid + document link
RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([0-9]+)$ ./index.php?vid=$1&mid=$2&document_srl=$3 [L,QSA]
# mid + entry title
RewriteRule ^([a-zA-Z0-9_]+)/entry/(.+)$ ./index.php?mid=$1&entry=$2 [L,QSA]
# vid + mid + entry title
RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/entry/(.+)$ ./index.php?vid=$1&mid=$2&entry=$3 [L,QSA]
#shop / vid / [category|product] / identifier
# router
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([a-zA-Z0-9_\.-]+)$ ./index.php?act=route&vid=$1&type=$2&identifier=$3 [L,QSA]
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^(.*)$ ./index.php [L]

View file

@ -160,11 +160,6 @@ module.exports = function(grunt) {
files: [
'Gruntfile.js',
'common/js/*.js',
'!common/js/html5.js',
'!common/js/jquery.js',
'!common/js/x.js',
'!common/js/xe.js',
'!common/js/modernizr.js',
'modules/admin/tpl/js/*.js',
'modules/board/tpl/js/*.js',
'modules/editor/tpl/js/*.js',
@ -175,9 +170,15 @@ module.exports = function(grunt) {
ignores : [
'**/jquery*.js',
'**/swfupload.js',
'**/*.min.js',
'**/**.min.js',
'**/*-packed.js',
'**/*.compressed.js'
'**/*.compressed.js',
'**/jquery-*.js',
'**/jquery.*.js',
'common/js/html5.js',
'common/js/x.js',
'common/js/xe.js',
'common/js/modernizr.js',
]
}
},

View file

@ -242,9 +242,18 @@ class Context
}
}
// check if using rewrite module
$this->allow_rewrite = ($this->db_info->use_rewrite == 'Y' ? TRUE : FALSE);
// If XE is installed, get virtual site information
if(self::isInstalled())
{
// If using rewrite module, initializes router
if($this->allow_rewrite)
{
Router::proc();
}
$oModuleModel = getModel('module');
$site_module_info = $oModuleModel->getDefaultMid();
@ -363,9 +372,6 @@ class Context
$this->lang = &$GLOBALS['lang'];
$this->loadLang(_XE_PATH_ . 'common/lang/');
// check if using rewrite module
$this->allow_rewrite = ($this->db_info->use_rewrite == 'Y' ? TRUE : FALSE);
// set locations for javascript use
$url = array();
$current_url = self::getRequestUri();
@ -1640,7 +1646,9 @@ class Context
'act.document_srl.key.mid.vid' => ($act == 'trackback') ? "$vid/$mid/$srl/$key/$act" : ''
);
$query = $target_map[$target];
Router::setMap($target_map);
$query = Router::makePrettyUrl($target);
}
if(!$query)

View file

@ -0,0 +1,253 @@
<?php
/**
* @file Router.class.php
* @brief Parses URIs and determines routing
* @author FunnyXE (admin@funnyxe.com)
*/
class Router
{
/**
* URI Segments
* @var array
*/
private static $segments = array();
/**
* Routes
* @var array
*/
private static $routes = array(
// rss , blogAPI
'(rss|atom)' => array('module' => 'rss', 'act' => '$1', '[L]' => TRUE),
'([a-zA-Z0-9_]+)/(rss|atom|api)' => array('mid' => '$1', 'act' => '$2', '[L]' => TRUE),
'([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/(rss|atom|api)' => array('vid' => '$1', 'mid' => '$2', 'act' => '$3', '[L]' => TRUE),
// trackback
'([0-9]+)/(.+)/trackback' => array('document_srl' => '$1', 'key' => '$2', 'act' => 'trackback', '[L]' => TRUE),
'([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback' => array('mid' => '$1', 'document_srl' => '$2', 'key' => '$3', 'act' => 'trackback', '[L]' => TRUE),
'([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback' => array('vid' => '$1', 'mid' => '$2', 'document_srl' => '$3' , 'key' => '$4', 'act' => 'trackback', '[L]' => TRUE),
// document_srl
'([0-9]+)' => array('document_srl' => '$1', '[L]' => TRUE),
// mid
'([a-zA-Z0-9_]+)/?' => array('mid' => '$1', '[L]' => TRUE),
// mid + document_srl
'([a-zA-Z0-9_]+)/([0-9]+)' => array('mid' => '$1', 'document_srl' => '$2', '[L]' => TRUE),
// vid + mid
'([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/' => array('vid' => '$1', 'mid' => '$2', '[L]' => TRUE),
// vid + mid + document_srl
'([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([0-9]+)?' => array('vid' => '$1', 'mid' => '$2', 'document_srl' => '$3', '[L]' => TRUE),
// mid + entry title
'([a-zA-Z0-9_]+)/entry/(.+)' => array('mid' => '$1', 'entry' => '$2', '[L]' => TRUE),
// vid + mid + entry title
'([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/entry/(.+)' => array('vid' => '$1', 'mid' => '$2', 'entry' => '$3', '[L]' => TRUE),
// shop / vid / [category|product] / identifier
'([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/([a-zA-Z0-9_\.-]+)' => array('act' => 'route', 'vid' => '$1', 'type' => '$2', 'identifier'=> '$3', '[L]' => TRUE)
);
/**
* Rewrite map
* @var array
*/
private static $rewrite_map = array();
/**
* @brief Applys routes.
* @see This function should be called only once
* @return void
*/
public static function proc()
{
$uri = $_SERVER['REQUEST_URI'];
if (stripos($uri, $_SERVER['SCRIPT_NAME']) === 0)
{
$uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
}
elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
{
$uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
}
if ($uri == '/' || empty($uri))
{
return;
}
// Get relative path from request uri
$path = parse_url($uri, PHP_URL_PATH);
// Do some final cleaning of the URI and return it
$path = str_replace(array('//', '../'), '/', trim($path, '/'));
if(strlen($path) > 0)
{
self::$segments = explode('/', $path);
}
if(isset(self::$routes[$path]))
{
foreach(self::$routes[$path] as $key => $val)
{
if(strlen($val) > 0)
{
if(substr_compare($val, '$', 0, 1) == 0)
{
$segment_index = (int) substr($val, 1) - 1;
if($segment_index < 0)
{
continue;
}
Context::set($key, self::$segments[$segment_index], TRUE);
}
else
{
Context::set($key, $val, TRUE);
}
}
else
{
Context::set($key, '', TRUE);
}
}
return;
}
$break = FALSE;
// Apply routes
foreach(self::$routes as $regex => $query)
{
// Stop the routing proccess
if($break)
{
break;
}
if(preg_match('#^' . $regex . '$#', $path, $matches))
{
foreach($query as $key => $val)
{
// If [L] keyword is defined
if($key == '[L]')
{
// Stop the routing process and don't apply any more rules
$break = TRUE;
continue;
}
if(strlen($val) > 0)
{
if(substr($val, 0, 1) == '$')
{
$segment_index = (int) substr($val, 1) - 1;
if($segment_index < 0)
{
continue;
}
Context::set($key, self::$segments[$segment_index], TRUE);
}
else
{
Context::set($key, $val, TRUE);
}
}
else
{
Context::set($key, '', TRUE);
}
}
}
}
}
/**
* @brief Add a rewrite map(s)
* @param array $map
* @return void
*/
public static function setMap($map)
{
self::$rewrite_map = array_merge(self::$rewrite_map, $map);
}
/**
* @brief Add a route
* @param string $target
* @param array $query
* @return void
*/
public static function add($target, $query)
{
self::$routes[$target] = $query;
}
/**
* @brief Add multiple routes
* @param array $routes
* @return void
*/
public function adds($routes)
{
self::$routes = array_merge(self::$routes, $routes);
}
/**
* @brief Get segment from request uri
* @param int $index
* @return string
*/
public static function getSegment($index)
{
return self::$segments[$index - 1];
}
/**
* @brief Get segment from request uri
* @param int $index
* @return string
*/
public static function getSegments()
{
return self::$segments;
}
/**
* @brief Get route info
* @param string $regex
* @return array
*/
public static function getRoute($regex)
{
return self::$routes[$regex];
}
/**
* @brief Get routes list
* @return array
*/
public static function getRoutes()
{
return self::$routes;
}
/**
* @brief Get routes list
* @param string $regex
* @return boolean
*/
public static function isExistsRoute($regex)
{
return isset(self::$routes[$regex]);
}
/**
* @brief Makes shortten url
* @param string $regex
* @return string
*/
public static function makePrettyUrl($regex)
{
return self::$rewrite_map[$regex];
}
}

View file

@ -0,0 +1,352 @@
<?php
/* Copyright (C) NAVER <http://www.navercorp.com> */
/**
* This class can be used to hash passwords using various algorithms and check their validity.
* It is fully compatible with previous defaults, while also supporting bcrypt and pbkdf2.
*
* @file Password.class.php
* @author Kijin Sung (kijin@kijinsung.com)
* @package /classes/security
* @version 1.1
*/
class Password
{
/**
* @brief Return the list of hashing algorithms supported by this server
* @return array
*/
public function getSupportedAlgorithms()
{
$retval = array();
if(function_exists('hash_hmac') && in_array('sha256', hash_algos()))
{
$retval['pbkdf2'] = 'pbkdf2';
}
if(version_compare(PHP_VERSION, '5.3.7', '>=') && defined('CRYPT_BLOWFISH'))
{
$retval['bcrypt'] = 'bcrypt';
}
$retval['md5'] = 'md5';
return $retval;
}
/**
* @brief Return the best hashing algorithm supported by this server
* @return string
*/
public function getBestAlgorithm()
{
$algos = $this->getSupportedAlgorithms();
return key($algos);
}
/**
* @brief Return the currently selected hashing algorithm
* @return string
*/
public function getCurrentlySelectedAlgorithm()
{
if(function_exists('getModel'))
{
$config = getModel('member')->getMemberConfig();
$algorithm = $config->password_hashing_algorithm;
if(strval($algorithm) === '')
{
$algorithm = 'md5'; // Historical default for XE
}
}
else
{
$algorithm = 'md5';
}
return $algorithm;
}
/**
* @brief Return the currently configured work factor for bcrypt and other adjustable algorithms
* @return int
*/
public function getWorkFactor()
{
if(function_exists('getModel'))
{
$config = getModel('member')->getMemberConfig();
$work_factor = $config->password_hashing_work_factor;
if(!$work_factor || $work_factor < 4 || $work_factor > 31)
{
$work_factor = 8; // Reasonable default
}
}
else
{
$work_factor = 8;
}
return $work_factor;
}
/**
* @brief Create a hash using the specified algorithm
* @param string $password The password
* @param string $algorithm The algorithm (optional)
* @return string
*/
public function createHash($password, $algorithm = null)
{
if($algorithm === null)
{
$algorithm = $this->getCurrentlySelectedAlgorithm();
}
if(!array_key_exists($algorithm, $this->getSupportedAlgorithms()))
{
return false;
}
$password = trim($password);
switch($algorithm)
{
case 'md5':
return md5($password);
case 'pbkdf2':
$iterations = pow(2, $this->getWorkFactor() + 5);
$salt = $this->createSecureSalt(12);
$hash = base64_encode($this->pbkdf2($password, $salt, 'sha256', $iterations, 24));
return 'sha256:'.sprintf('%07d', $iterations).':'.$salt.':'.$hash;
case 'bcrypt':
return $this->bcrypt($password);
default:
return false;
}
}
/**
* @brief Check if a password matches a hash
* @param string $password The password
* @param string $hash The hash
* @param string $algorithm The algorithm (optional)
* @return bool
*/
public function checkPassword($password, $hash, $algorithm = null)
{
if($algorithm === null)
{
$algorithm = $this->checkAlgorithm($hash);
}
$password = trim($password);
switch($algorithm)
{
case 'md5':
return md5($password) === $hash || md5(sha1(md5($password))) === $hash;
case 'mysql_old_password':
return (class_exists('Context') && substr(Context::getDBType(), 0, 5) === 'mysql') ?
DB::getInstance()->isValidOldPassword($password, $hash) : false;
case 'mysql_password':
return $hash[0] === '*' && substr($hash, 1) === strtoupper(sha1(sha1($password, true)));
case 'pbkdf2':
$hash = explode(':', $hash);
$hash[3] = base64_decode($hash[3]);
$hash_to_compare = $this->pbkdf2($password, $hash[2], $hash[0], intval($hash[1], 10), strlen($hash[3]));
return $this->strcmpConstantTime($hash_to_compare, $hash[3]);
case 'bcrypt':
$hash_to_compare = $this->bcrypt($password, $hash);
return $this->strcmpConstantTime($hash_to_compare, $hash);
default:
return false;
}
}
/**
* @brief Check the algorithm used to create a hash
* @param string $hash The hash
* @return string
*/
function checkAlgorithm($hash)
{
if(preg_match('/^\$2[axy]\$([0-9]{2})\$/', $hash, $matches))
{
return 'bcrypt';
}
elseif(preg_match('/^sha[0-9]+:([0-9]+):/', $hash, $matches))
{
return 'pbkdf2';
}
elseif(strlen($hash) === 32 && ctype_xdigit($hash))
{
return 'md5';
}
elseif(strlen($hash) === 16 && ctype_xdigit($hash))
{
return 'mysql_old_password';
}
elseif(strlen($hash) === 41 && $hash[0] === '*')
{
return 'mysql_password';
}
else
{
return false;
}
}
/**
* @brief Check the work factor of a hash
* @param string $hash The hash
* @return int
*/
function checkWorkFactor($hash)
{
if(preg_match('/^\$2[axy]\$([0-9]{2})\$/', $hash, $matches))
{
return intval($matches[1], 10);
}
elseif(preg_match('/^sha[0-9]+:([0-9]+):/', $hash, $matches))
{
return max(0, round(log($matches[1], 2)) - 5);
}
else
{
return false;
}
}
/**
* @brief Generate a cryptographically secure random string to use as a salt
* @param int $length The number of bytes to return
* @param string $format hex or alnum
* @return string
*/
public function createSecureSalt($length, $format = 'hex')
{
// Find out how many bytes of entropy we really need
$entropy_required_bytes = ceil(($format === 'hex') ? ($length / 2) : ($length * 3 / 4));
// Cap entropy to 256 bits from any one source, because anything more is meaningless
$entropy_capped_bytes = min(32, $entropy_required_bytes);
// Find and use the most secure way to generate a random string
$is_windows = (defined('PHP_OS') && strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
if(function_exists('openssl_random_pseudo_bytes') && (!$is_windows || version_compare(PHP_VERSION, '5.4', '>=')))
{
$entropy = openssl_random_pseudo_bytes($entropy_capped_bytes);
}
elseif(function_exists('mcrypt_create_iv') && (!$is_windows || version_compare(PHP_VERSION, '5.3.7', '>=')))
{
$entropy = mcrypt_create_iv($entropy_capped_bytes, MCRYPT_DEV_URANDOM);
}
elseif(function_exists('mcrypt_create_iv') && $is_windows)
{
$entropy = mcrypt_create_iv($entropy_capped_bytes, MCRYPT_RAND);
}
elseif(!$is_windows && @is_readable('/dev/urandom'))
{
$fp = fopen('/dev/urandom', 'rb');
$entropy = fread($fp, $entropy_capped_bytes);
fclose($fp);
}
else
{
$entropy = '';
for($i = 0; $i < $entropy_capped_bytes; $i += 2)
{
$entropy .= pack('S', rand(0, 65536) ^ mt_rand(0, 65535));
}
}
// Mixing (see RFC 4086 section 5)
$output = '';
for($i = 0; $i < $entropy_required_bytes; $i += 32)
{
$output .= hash('sha256', $entropy . $i . rand(), true);
}
// Encode and return the random string
if($format === 'hex')
{
return substr(bin2hex($output), 0, $length);
}
else
{
$salt = substr(base64_encode($output), 0, $length);
$replacements = chr(rand(65, 90)) . chr(rand(97, 122)) . rand(0, 9);
return strtr($salt, '+/=', $replacements);
}
}
/**
* @brief Generate the PBKDF2 hash of a string using a salt
* @param string $password The password
* @param string $salt The salt
* @param string $algorithm The algorithm (optional, default is sha256)
* @param int $iterations Iteration count (optional, default is 8192)
* @param int $length The length of the hash (optional, default is 32)
* @return string
*/
public function pbkdf2($password, $salt, $algorithm = 'sha256', $iterations = 8192, $length = 24)
{
if(function_exists('hash_pbkdf2'))
{
return hash_pbkdf2($algorithm, $password, $salt, $iterations, $length, true);
}
else
{
$output = '';
$block_count = ceil($length / strlen(hash($algorithm, '', true))); // key length divided by the length of one hash
for($i = 1; $i <= $block_count; $i++)
{
$last = $salt . pack('N', $i); // $i encoded as 4 bytes, big endian
$last = $xorsum = hash_hmac($algorithm, $last, $password, true); // first iteration
for($j = 1; $j < $iterations; $j++) // The other $count - 1 iterations
{
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
return substr($output, 0, $length);
}
}
/**
* @brief Generate the bcrypt hash of a string using a salt
* @param string $password The password
* @param string $salt The salt (optional, auto-generated if empty)
* @return string
*/
public function bcrypt($password, $salt = null)
{
if($salt === null)
{
$salt = '$2y$'.sprintf('%02d', $this->getWorkFactor()).'$'.$this->createSecureSalt(22, 'alnum');
}
return crypt($password, $salt);
}
/**
* @brief Compare two strings in constant time
* @param string $a The first string
* @param string $b The second string
* @return bool
*/
function strcmpConstantTime($a, $b)
{
$diff = strlen($a) ^ strlen($b);
$maxlen = min(strlen($a), strlen($b));
for($i = 0; $i < $maxlen; $i++)
{
$diff |= ord($a[$i]) ^ ord($b[$i]);
}
return $diff === 0;
}
}
/* End of file : Password.class.php */
/* Location: ./classes/security/Password.class.php */

View file

@ -1,4 +1,5 @@
(function($){
"use strict";
var App = window.xe.getApp('xeEditorApp')[0];
var CK = window.CKEDITOR;

View file

@ -299,6 +299,7 @@ if(!defined('__XE_LOADED_CLASS__'))
require(_XE_PATH_ . 'classes/xml/XmlJsFilter.class.php');
require(_XE_PATH_ . 'classes/xml/XmlLangParser.class.php');
require(_XE_PATH_ . 'classes/cache/CacheHandler.class.php');
require(_XE_PATH_ . 'classes/router/Router.class.php');
require(_XE_PATH_ . 'classes/context/Context.class.php');
require(_XE_PATH_ . 'classes/db/DB.class.php');
require(_XE_PATH_ . 'classes/file/FileHandler.class.php');
@ -313,6 +314,7 @@ if(!defined('__XE_LOADED_CLASS__'))
require(_XE_PATH_ . 'classes/mobile/Mobile.class.php');
require(_XE_PATH_ . 'classes/validator/Validator.class.php');
require(_XE_PATH_ . 'classes/frontendfile/FrontEndFileHandler.class.php');
require(_XE_PATH_ . 'classes/security/Password.class.php');
require(_XE_PATH_ . 'classes/security/Security.class.php');
require(_XE_PATH_ . 'classes/security/IpFilter.class.php');
if(__DEBUG__)

View file

@ -797,6 +797,7 @@ jQuery(function($){
});
if(typeof console == 'undefined'){
/* jshint -W020 */
console={log:function(){}};
}

View file

@ -26,6 +26,7 @@
<script>
(function($){
"use strict";
var editor_primary_key_name = '{$editor_primary_key_name}';
var editor_content_key_name = '{$editor_content_key_name}';

View file

@ -1,22 +1,20 @@
(function($){
var xeEditorApp = xe.createApp('xeEditorApp', {
init : function() {
console.log('INIT @ xeEditorApp')
"use strict";
var XeEditorApp = xe.createApp('xeEditorApp', {
init : function() {
},
API_ONREADY : function() {
console.log('ONREADY @ xeEditorApp');
},
getContent : function(seq) {
this.cast('GET_CONTENT');
},
API_EDITOR_CREATED : function(){
console.log('APP @ API_EDITOR_CREATED');
},
});
// Shortcut function in jQuery
$.fn.xeEditorApp = function(opts) {
var u = new xeEditorApp(this.eq(0), opts);
var u = new XeEditorApp(this.eq(0), opts);
if(u) xe.registerApp(u);
return u;
@ -24,12 +22,11 @@
// Shortcut function in XE
window.xe.createXeEditor = function() {
var u = new xeEditorApp();
// if(u) xe.registerApp(u);
var u = new XeEditorApp();
return u;
};
var u = new xeEditorApp();
var u = new XeEditorApp();
xe.registerApp(u);
})(jQuery);

View file

@ -1699,6 +1699,21 @@
<value xml:lang="en"><![CDATA[password strength]]></value>
<value xml:lang="jp"><![CDATA[パスワード強度]]></value>
</item>
<item name="cmd_password_hashing_algorithm">
<value xml:lang="ko"><![CDATA[비밀번호 암호화 알고리듬]]></value>
<value xml:lang="en"><![CDATA[Password Hashing Algorithm]]></value>
<value xml:lang="jp"><![CDATA[パスワードの暗号化アルゴリズム]]></value>
</item>
<item name="cmd_password_hashing_work_factor">
<value xml:lang="ko"><![CDATA[비밀번호 암호화 소요시간]]></value>
<value xml:lang="en"><![CDATA[Password Hashing Work Factor]]></value>
<value xml:lang="jp"><![CDATA[暗号化のワーク·ファクター]]></value>
</item>
<item name="cmd_password_hashing_auto_upgrade">
<value xml:lang="ko"><![CDATA[알고리듬 자동 업그레이드]]></value>
<value xml:lang="en"><![CDATA[Auto-upgrade Hashing Algorithm]]></value>
<value xml:lang="jp"><![CDATA[暗号化アルゴリズム自動アップグレード]]></value>
</item>
<item name="password_strength_low">
<value xml:lang="ko"><![CDATA[낮음]]></value>
@ -1721,6 +1736,21 @@
<value xml:lang="en"><![CDATA[When members register or change the password, the password must meet the specified password strength. However, the administrator is an exception.]]></value>
<value xml:lang="jp"><![CDATA[会員がパスワードを登録する際に、パスワードが一定の強度を満たす必要があります。ただし、管理者が直接に登録する際には適用されません。]]></value>
</item>
<item name="about_password_hashing_algorithm">
<value xml:lang="ko"><![CDATA[회원들의 비밀번호를 DB에 저장할 때 암호화(해싱)하는 방식을 지정할 수 있습니다.]]></value>
<value xml:lang="en"><![CDATA[You can choose how to encrypt (hash) members' passwords stored in the database.]]></value>
<value xml:lang="jp"><![CDATA[会員のパスワードをDBに保存するときに暗号化ハッシュする方法を指定することができます。]]></value>
</item>
<item name="about_password_hashing_work_factor">
<value xml:lang="ko"><![CDATA[시간이 오래 걸리는 알고리듬일수록 보안이 강하지만, 로그인이 오래 걸릴 수 있습니다. bcrypt 및 pbkdf2 알고리듬에만 적용됩니다.]]></value>
<value xml:lang="en"><![CDATA[Higher work factors are more secure, but logins may take a long time. This only applies to bcrypt and pbkdf2.]]></value>
<value xml:lang="jp"><![CDATA[時間がかかるアルゴリズムほどセキュリティが強いが、ログインがかかることがあります。 bcryptとpbkdf2アルゴリズムにのみ適用されます。]]></value>
</item>
<item name="about_password_hashing_auto_upgrade">
<value xml:lang="ko"><![CDATA[설정된 알고리듬과 다른 방법으로 암호화된 비밀번호가 있으면 다음 로그인시 설정된 알고리듬으로 자동 변환합니다.]]></value>
<value xml:lang="en"><![CDATA[Passwords encrypted using different algorithms will be automatically converted to the configured algorithm at next login.]]></value>
<value xml:lang="jp"><![CDATA[設定されたアルゴリズムとは異なる方法で暗号化されたパスワードがあれば、次のログイン時に設定されたアルゴリズムに自動的に変換します。]]></value>
</item>
<item name="about_password_strength" type="array">
<item name="low">

View file

@ -159,8 +159,31 @@ class memberAdminController extends member
'enable_confirm',
'webmaster_name',
'webmaster_email',
'password_strength'
'password_strength',
'password_hashing_algorithm',
'password_hashing_work_factor',
'password_hashing_auto_upgrade'
);
$oPassword = new Password();
if(!array_key_exists($args->password_hashing_algorithm, $oPassword->getSupportedAlgorithms()))
{
$args->password_hashing_algorithm = 'md5';
}
$args->password_hashing_work_factor = intval($args->password_hashing_work_factor, 10);
if($args->password_hashing_work_factor < 4)
{
$args->password_hashing_work_factor = 4;
}
if($args->password_hashing_work_factor > 16)
{
$args->password_hashing_work_factor = 16;
}
if($args->password_hashing_auto_upgrade != 'Y')
{
$args->password_hashing_auto_upgrade = 'N';
}
if((!$args->webmaster_name || !$args->webmaster_email) && $args->enable_confirm == 'Y')
{

View file

@ -129,8 +129,10 @@ class memberAdminView extends member
*/
public function dispMemberAdminConfig()
{
$oPassword = new Password();
Context::set('password_hashing_algos', $oPassword->getSupportedAlgorithms());
$this->setTemplateFile('default_config');
}
public function dispMemberAdminSignUpConfig()

View file

@ -71,6 +71,20 @@ class member extends ModuleObject {
if($config->group_image_mark!='Y') $config->group_image_mark = 'N';
if(!$config->password_strength) $config->password_strength = 'normal';
if(!$config->password_hashing_algorithm)
{
$oPassword = new Password();
$config->password_hashing_algorithm = $oPassword->getBestAlgorithm();
}
if(!$config->password_hashing_work_factor)
{
$config->password_hashing_work_factor = 8;
}
if(!$config->password_hashing_auto_upgrade)
{
$config->password_hashing_auto_upgrade = 'Y';
}
global $lang;
$oMemberModel = getModel('member');
// Create a member controller object

View file

@ -1119,7 +1119,7 @@ class memberController extends member
}
else
{
$args->password = md5($output->data->new_password);
$args->password = getModel('member')->hashPassword($args->password);
unset($args->denied);
}
// Back up the value of $Output->data->is_register
@ -1974,7 +1974,7 @@ class memberController extends member
$message = Context::getLang('about_password_strength');
return new Object(-1, $message[$config->password_strength]);
}
$args->password = md5($args->password);
$args->password = $oMemberModel->hashPassword($args->password);
}
elseif(!$args->password) unset($args->password);
if($oMemberModel->isDeniedID($args->user_id)) return new Object(-1,'denied_user_id');
@ -2167,7 +2167,7 @@ class memberController extends member
return new Object(-1, $message[$config->password_strength]);
}
$args->password = md5($args->password);
$args->password = $oMemberModel->hashPassword($args->password);
}
else $args->password = $orgMemberInfo->password;
if(!$args->user_name) $args->user_name = $orgMemberInfo->user_name;
@ -2257,14 +2257,7 @@ class memberController extends member
return new Object(-1, $message[$config->password_strength]);
}
if($this->useSha1)
{
$args->password = md5(sha1(md5($args->password)));
}
else
{
$args->password = md5($args->password);
}
$args->password = $oMemberModel->hashPassword($args->password);
}
else if($args->hashed_password)
{

View file

@ -1006,65 +1006,71 @@ class memberModel extends member
/**
* @brief Compare plain text password to the password saved in DB
* @param string $hashed_password The hash that was saved in DB
* @param string $password_text The password to check
* @param int $member_srl Set this to member_srl when comparing a member's password (optional)
* @return bool
*/
function isValidPassword($hashed_password, $password_text, $member_srl=null)
{
// False if no password in entered
if(!$password_text) return false;
$isSha1 = ($this->useSha1 && function_exists('sha1'));
// Return true if the user input is equal to md5 hash value
if($hashed_password == md5($password_text))
if(!$password_text)
{
if($isSha1 && $member_srl > 0)
return false;
}
// Check the password
$oPassword = new Password();
$current_algorithm = $oPassword->checkAlgorithm($hashed_password);
$match = $oPassword->checkPassword($password_text, $hashed_password, $current_algorithm);
if(!$match)
{
return false;
}
// Update the encryption method if necessary
$config = $this->getMemberConfig();
if($member_srl > 0 && $config->password_hashing_auto_upgrade != 'N')
{
$need_upgrade = false;
if(!$need_upgrade)
{
$required_algorithm = $oPassword->getCurrentlySelectedAlgorithm();
if($required_algorithm !== $current_algorithm) $need_upgrade = true;
}
if(!$need_upgrade)
{
$required_work_factor = $oPassword->getWorkFactor();
$current_work_factor = $oPassword->checkWorkFactor($hashed_password);
if($current_work_factor !== false && $required_work_factor > $current_work_factor) $need_upgrade = true;
}
if($need_upgrade === true)
{
$args = new stdClass();
$args->member_srl = $member_srl;
$args->hashed_password = md5(sha1(md5($password_text)));
$args->hashed_password = $this->hashPassword($password_text, $required_algorithm);
$oMemberController = getController('member');
$oMemberController->updateMemberPassword($args);
}
return true;
}
// Return true if the user input is equal to the value of mysql_pre4_hash_password
if(mysql_pre4_hash_password($password_text) == $hashed_password)
{
if($isSha1 && $member_srl > 0)
{
$args = new stdClass();
$args->member_srl = $member_srl;
$args->hashed_password = md5(sha1(md5($password_text)));
$oMemberController = getController('member');
$oMemberController->updateMemberPassword($args);
}
return true;
}
// Verify the password by using old_password if the current db is MySQL. If correct, return true.
if(substr(Context::getDBType(),0,5)=='mysql')
{
$oDB = &DB::getInstance();
if($oDB->isValidOldPassword($password_text, $hashed_password))
{
if($isSha1 && $member_srl > 0)
{
$args = new stdClass();
$args->member_srl = $member_srl;
$args->hashed_password = md5(sha1(md5($password_text)));
$oMemberController = getController('member');
$oMemberController->updateMemberPassword($args);
}
return true;
}
}
if($isSha1 && $hashed_password == md5(sha1(md5($password_text)))) return true;
return false;
return true;
}
/**
* @brief Create a hash of plain text password
* @param string $password_text The password to hash
* @param string $algorithm The algorithm to use (optional, only set this when you want to use a non-default algorithm)
* @return string
*/
function hashPassword($password_text, $algorithm = null)
{
$oPassword = new Password();
return $oPassword->createHash($password_text, $algorithm);
}
function checkPasswordStrength($password, $strength)
{

View file

@ -7,5 +7,8 @@
<field name="webmaster_name" required="true" length="2:40" />
<field name="webmaster_email" length="1:200" rule="email" />
<field name="password_strength" required="true" />
<field name="password_hashing_algorithm" required="true" />
<field name="password_hashing_work_factor" required="true" />
<field name="password_hashing_auto_upgrade" required="true" />
</fields>
</ruleset>

View file

@ -29,6 +29,32 @@
<p class="x_help-block">{$lang->about_password_strength_config}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->cmd_password_hashing_algorithm}</label>
<div class="x_controls">
<select name="password_hashing_algorithm" id="password_hashing_algorithm" style="width:auto">
<option value="{$key}" selected="selected"|cond="$config->password_hashing_algorithm==$key" loop="$password_hashing_algos=>$key,$val">{$val}</option>
</select>
<p class="x_help-block">{$lang->about_password_hashing_algorithm}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->cmd_password_hashing_work_factor}</label>
<div class="x_controls">
<select name="password_hashing_work_factor" id="password_hashing_work_factor" style="width:auto">
<option value="{$i}" selected="selected"|cond="$config->password_hashing_work_factor==$i" loop="$i=4;$i<=16;$i++">{sprintf('%02d', $i)}</option>
</select>
<p class="x_help-block">{$lang->about_password_hashing_work_factor}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label">{$lang->cmd_password_hashing_auto_upgrade}</label>
<div class="x_controls">
<label for="password_hashing_auto_upgrade_y" class="x_inline"><input type="radio" name="password_hashing_auto_upgrade" id="password_hashing_auto_upgrade_y" value="Y" checked="checked"|cond="$config->password_hashing_auto_upgrade == 'Y'" /> {$lang->cmd_yes}</label>
<label for="password_hashing_auto_upgrade_n" class="x_inline"><input type="radio" name="password_hashing_auto_upgrade" id="password_hashing_auto_upgrade_n" value="N" checked="checked"|cond="$config->password_hashing_auto_upgrade != 'Y'" /> {$lang->cmd_no}</label>
<p class="x_help-block">{$lang->about_password_hashing_auto_upgrade}</p>
</div>
</div>
<div class="x_control-group">
<label class="x_control-label" for="webmaster_name">{$lang->webmaster_name}</label>
<div class="x_controls">