397 lines
11 KiB
PHP
397 lines
11 KiB
PHP
<?php
|
|
namespace sammo;
|
|
|
|
/**
|
|
* Session Wrapper. 내부적으로 $_SESSION을 이용
|
|
*
|
|
* @property int $userID 유저코드
|
|
* @property string $userName 유저명
|
|
* @property int $userGrade 유저등급
|
|
* @property string $ip IP
|
|
* @property bool $reqOTP 인증 코드 필요
|
|
* @property array $acl 권한
|
|
* @property int|null $tokenID 토큰ID
|
|
* @property string $tokenValidUntil 로그인 토큰 길이
|
|
*
|
|
* @property int $generalID 장수 번호 (게임 로그인 필요)
|
|
* @property string $generalName 장수 이름 (게임 로그인 필요)
|
|
*/
|
|
class Session
|
|
{
|
|
const PROTECTED_NAMES = [
|
|
'ip'=>true,
|
|
'reqOTP'=>true,
|
|
'time'=>true,
|
|
'userID'=>true,
|
|
'userName'=>true,
|
|
'userGrade'=>true,
|
|
'writeClosed'=>true,
|
|
'generalID'=>true,
|
|
'generalName'=>true,
|
|
'tokenValidUntil'=>true,
|
|
'acl'=>true
|
|
];
|
|
|
|
const GAME_KEY_DATE = '_g_loginDate';
|
|
const GAME_KEY_GENERAL_ID = '_g_no';
|
|
const GAME_KEY_GENERAL_NAME = '_g_name';
|
|
const GAME_KEY_EXPECTED_DEADTIME = '_g_deadtime';
|
|
|
|
|
|
private $writeClosed = false;
|
|
private $sessionID = null;
|
|
|
|
public static function getInstance(): Session
|
|
{
|
|
static $inst = null;
|
|
if ($inst === null) {
|
|
$inst = new Session();
|
|
}
|
|
return $inst;
|
|
}
|
|
|
|
public function restart(): Session
|
|
{
|
|
//NOTE: logout 프로세스는 아예 세션을 날려버리기도 하므로, 항상 안전하게 session_restart가 가능함을 보장하지 않음.
|
|
ini_set('session.use_only_cookies', 'false');
|
|
ini_set('session.use_cookies', 'false');
|
|
ini_set('session.use_trans_sid', 'false');
|
|
ini_set('session.cache_limiter', "none");
|
|
session_id($this->sessionID);
|
|
session_start(); // second session_start
|
|
$this->writeClosed = false;
|
|
return $this;
|
|
}
|
|
|
|
private static function die($result)
|
|
{
|
|
if (is_string($result)) {
|
|
header('Location:'.$result);
|
|
die();
|
|
}
|
|
|
|
$jsonResult = [
|
|
'result'=>false,
|
|
'reason'=>'로그인이 필요합니다.'
|
|
];
|
|
|
|
if (!is_array($result)) {
|
|
Json::die($jsonResult);
|
|
}
|
|
|
|
Json::die($result + $jsonResult);
|
|
}
|
|
|
|
public static function requireLogin($actionOnError = '..'): Session
|
|
{
|
|
$session = static::getInstance();
|
|
if ($session->isLoggedIn()) {
|
|
return $session;
|
|
}
|
|
|
|
static::die($actionOnError);
|
|
}
|
|
|
|
public static function requireGameLogin($actionOnError = '..'): Session
|
|
{
|
|
$session = static::requireLogin($actionOnError)->loginGame();
|
|
|
|
if ($session->generalID) {
|
|
return $session;
|
|
}
|
|
|
|
static::die($actionOnError);
|
|
}
|
|
|
|
public function __construct()
|
|
{
|
|
//session_cache_limiter('nocache, must_revalidate');
|
|
|
|
// 세션 변수의 등록
|
|
if (session_id() == "") {
|
|
session_start();
|
|
$this->sessionID = session_id();
|
|
}
|
|
|
|
//첫 등장
|
|
|
|
if (!$this->get('ip')) {
|
|
$this->set('ip', Util::get_client_ip(true));
|
|
$this->set('time', time());
|
|
}
|
|
}
|
|
|
|
public function setReadOnly(): Session
|
|
{
|
|
if (!$this->writeClosed) {
|
|
session_write_close();
|
|
$this->writeClosed = true;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
public function __set(string $name, $value)
|
|
{
|
|
if (key_exists($name, self::PROTECTED_NAMES)) {
|
|
trigger_error("{$name}은 외부에서 쓰기 금지된 Session 변수입니다.", E_USER_NOTICE);
|
|
return;
|
|
}
|
|
|
|
$this->set($name, $value);
|
|
}
|
|
|
|
private function set(string $name, $value)
|
|
{
|
|
if ($value === null) {
|
|
unset($_SESSION[$name]);
|
|
} else {
|
|
$_SESSION[$name] = $value;
|
|
}
|
|
}
|
|
|
|
public function __get(string $name)
|
|
{
|
|
if ($name == 'generalID') {
|
|
if (!class_exists('\\sammo\\UniqueConst')){
|
|
return null;
|
|
}
|
|
return $this->get(UniqueConst::$serverID.static::GAME_KEY_GENERAL_ID);
|
|
}
|
|
if ($name == 'generalName') {
|
|
if (!class_exists('\\sammo\\UniqueConst')){
|
|
return null;
|
|
}
|
|
return $this->get(UniqueConst::$serverID.static::GAME_KEY_GENERAL_NAME);
|
|
}
|
|
return $this->get($name);
|
|
}
|
|
|
|
private function get(string $name)
|
|
{
|
|
return Util::array_get($_SESSION[$name]);
|
|
}
|
|
|
|
public function login(int $userID, string $userName, int $grade, bool $reqOTP, ?string $tokenValidUntil, ?int $tokenID, array $acl): Session
|
|
{
|
|
$this->set('userID', $userID);
|
|
$this->set('userName', $userName);
|
|
$this->set('ip', Util::get_client_ip(true));
|
|
$this->set('time', time());
|
|
$this->set('userGrade', $grade);
|
|
$this->set('acl', $acl);
|
|
$this->set('reqOTP', $reqOTP);
|
|
$this->set('tokenValidUntil', $tokenValidUntil);
|
|
$this->set('tokenID', $tokenID);
|
|
return $this;
|
|
}
|
|
|
|
public function setReqOTP(bool $reqOTP=false, string $tokenValidUntil){
|
|
$this->set('reqOTP', $reqOTP);
|
|
$this->set('tokenValidUntil', $tokenValidUntil);
|
|
}
|
|
|
|
|
|
public function logout(): Session
|
|
{
|
|
if ($this->writeClosed) {
|
|
$this->restart();
|
|
}
|
|
if (class_exists('\\sammo\\UniqueConst')) {
|
|
$this->logoutGame();
|
|
}
|
|
|
|
if($this->tokenID??null){
|
|
RootDB::db()->delete('login_token', 'id = %i', $this->tokenID);
|
|
}
|
|
|
|
$this->set('userID', null);
|
|
$this->set('userName', null);
|
|
$this->set('userGrade', null);
|
|
$this->set('acl', null);
|
|
$this->set('reqOTP', null);
|
|
$this->set('time', time());
|
|
$this->set('lastMsgGet', null);
|
|
return $this;
|
|
}
|
|
|
|
public function loginGame(&$result = null): Session
|
|
{
|
|
$userID = $this->userID;
|
|
if (!$userID) {
|
|
if ($result !== null) {
|
|
$result = false;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
if (!class_exists('\\sammo\\DB') || !class_exists('\\sammo\\UniqueConst')) {
|
|
if ($result !== null) {
|
|
$result = false;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
$serverID = UniqueConst::$serverID;
|
|
|
|
$loginDate = $this->get($serverID.static::GAME_KEY_DATE);
|
|
$generalID = $this->get($serverID.static::GAME_KEY_GENERAL_ID);
|
|
$generalName = $this->get($serverID.static::GAME_KEY_GENERAL_NAME);
|
|
$deadTime = $this->get($serverID.static::GAME_KEY_EXPECTED_DEADTIME);
|
|
|
|
$now = time();
|
|
if (
|
|
$generalID && $generalName && $loginDate && $deadTime
|
|
&& $loginDate + 1800 > $now && $deadTime > $now
|
|
) {
|
|
//로그인 정보는 30분간 유지한다.
|
|
if ($result !== null) {
|
|
$result = true;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
if ($generalID || $generalName || $loginDate || $deadTime) {
|
|
$this->logoutGame();
|
|
}
|
|
|
|
$db = DB::db();
|
|
$gameStor = KVStorage::getStorage($db, 'game_env');
|
|
|
|
$general = $db->queryFirstRow(
|
|
'SELECT `no`, `name`, `killturn`, `turntime` from general where `owner` = %i',
|
|
$userID
|
|
);
|
|
if (!$general) {
|
|
if ($result !== null) {
|
|
$result = false;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
$turnterm = $gameStor->turnterm;
|
|
$isUnited = $gameStor->isunited != 0;
|
|
|
|
$generalID = $general['no'];
|
|
$generalName = $general['name'];
|
|
$nextTurn = new \DateTime($general['turntime']);
|
|
$nextTurn = $nextTurn->getTimestamp();
|
|
|
|
$deadTime = $nextTurn + $general['killturn'] * $turnterm;
|
|
if ($deadTime < $now && !$isUnited) {
|
|
$locked = $db->queryFirstField('SELECT plock FROM plock LIMIT 1');
|
|
if (!$locked) {
|
|
if ($result !== null) {
|
|
$result = false;
|
|
}
|
|
return $this;
|
|
}
|
|
}
|
|
|
|
$db->update('general', [
|
|
'logcnt' => $db->sqleval('logcnt+1'),
|
|
'ip' => Util::get_client_ip(true),
|
|
'lastConnect' => TimeUtil::now()
|
|
], 'owner = %i', $userID);
|
|
|
|
$this->set($serverID.static::GAME_KEY_DATE, $now);
|
|
$this->set($serverID.static::GAME_KEY_GENERAL_ID, $generalID);
|
|
$this->set($serverID.static::GAME_KEY_GENERAL_NAME, $generalName);
|
|
$this->set($serverID.static::GAME_KEY_EXPECTED_DEADTIME, $deadTime);
|
|
return $this;
|
|
}
|
|
|
|
public function logoutGame(): Session
|
|
{
|
|
if ($this->writeClosed) {
|
|
$this->restart();
|
|
}
|
|
$serverID = UniqueConst::$serverID;
|
|
$this->set($serverID.static::GAME_KEY_DATE, null);
|
|
$this->set($serverID.static::GAME_KEY_GENERAL_ID, null);
|
|
$this->set($serverID.static::GAME_KEY_GENERAL_NAME, null);
|
|
$this->set($serverID.static::GAME_KEY_EXPECTED_DEADTIME, null);
|
|
return $this;
|
|
}
|
|
|
|
|
|
/**
|
|
* 로그인 유저의 전역 grade를 받아옴
|
|
* @return int|null
|
|
*/
|
|
public static function getUserGrade(bool $requireLogin = false, string $exitPath = '..')
|
|
{
|
|
if ($requireLogin) {
|
|
$obj = self::requireLogin($exitPath);
|
|
} else {
|
|
$obj = self::getInstance();
|
|
}
|
|
|
|
return $obj->userGrade;
|
|
}
|
|
|
|
/**
|
|
* 로그인한 유저의 전역 id(숫자)를 받아옴
|
|
*
|
|
* @return int|null
|
|
*/
|
|
public static function getUserID(bool $requireLogin = false, string $exitPath = '..')
|
|
{
|
|
if ($requireLogin) {
|
|
$obj = self::requireLogin($exitPath);
|
|
} else {
|
|
$obj = self::getInstance();
|
|
}
|
|
|
|
return $obj->userID;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public static function getGeneralID(bool $requireLogin = false, string $exitPath = '..')
|
|
{
|
|
if ($requireLogin) {
|
|
$obj = self::requireLogin($exitPath);
|
|
} else {
|
|
$obj = self::getInstance();
|
|
}
|
|
|
|
return $obj->generalID??0;
|
|
}
|
|
|
|
public function isLoggedIn(bool $ignoreOTP = false): bool
|
|
{
|
|
if(!$ignoreOTP){
|
|
if($this->reqOTP){
|
|
return false;
|
|
}
|
|
if(!$this->tokenValidUntil){
|
|
return false;
|
|
}
|
|
$now = TimeUtil::now();
|
|
if($this->tokenValidUntil < $now){
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ($this->userID) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function isGameLoggedIn(): bool
|
|
{
|
|
if ($this->generalID) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function __destruct()
|
|
{
|
|
}
|
|
}
|