<?php
/**
 * @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
 * @copyright Copyright (c) 2017 Pellaeon Lin <pellaeon@hs.ntnu.edu.tw>
 * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
 *
 * @author Julius Härtl <jus@bitgrid.net>
 * @author Pellaeon Lin <pellaeon@hs.ntnu.edu.tw>
 * @author Lukas Reschke <lukas@statuscode.ch>
 *
 * @license GNU AGPL version 3 or any later version
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

namespace OCA\Registration\Service;

use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\IProvider;
use OC\Authentication\Token\IToken;
use OCA\Registration\Db\Registration;
use OCA\Registration\Db\RegistrationMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use \OCP\AppFramework\Http\TemplateResponse;
use \OCP\AppFramework\Http\RedirectResponse;
use \OCP\Defaults;
use OCP\ILogger;
use OCP\IRequest;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\Security\ICrypto;
use OCP\Session\Exceptions\SessionNotAvailableException;
use \OCP\IUserManager;
use \OCP\IUserSession;
use \OCP\IGroupManager;
use \OCP\IL10N;
use \OCP\IConfig;
use \OCP\Security\ISecureRandom;

class RegistrationService {

	/** @var string */
	private $appName;
	/** @var MailService */
	private $mailService;
	/** @var IL10N */
	private $l10n;
	/** @var IURLGenerator */
	private $urlGenerator;
	/** @var RegistrationMapper */
	private $registrationMapper;
	/** @var IUserManager */
	private $userManager;
	/** @var IConfig */
	private $config;
	/** @var IGroupManager */
	private $groupManager;
	/** @var \OCP\Defaults */
	private $defaults;
	/** @var ISecureRandom */
	private $random;
	/** @var IUserSession  */
	private $usersession;
	/** @var IRequest */
	private $request;
	/** @var ILogger */
	private $logger;
	/** @var ISession */
	private $session;
	/** @var IProvider */
	private $tokenProvider;
	/** @var ICrypto */
	private $crypto;

	public function __construct($appName, MailService $mailService, IL10N $l10n, IURLGenerator $urlGenerator,
								RegistrationMapper $registrationMapper, IUserManager $userManager, IConfig $config, IGroupManager $groupManager, Defaults $defaults,
								ISecureRandom $random, IUserSession $us, IRequest $request, ILogger $logger, ISession $session, IProvider $tokenProvider, ICrypto $crypto){
		$this->appName = $appName;
		$this->mailService = $mailService;
		$this->l10n = $l10n;
		$this->urlGenerator = $urlGenerator;
		$this->registrationMapper = $registrationMapper;
		$this->userManager = $userManager;
		$this->config = $config;
		$this->groupManager = $groupManager;
		$this->defaults = $defaults;
		$this->random = $random;
		$this->usersession = $us;
		$this->request = $request;
		$this->logger = $logger;
		$this->session = $session;
		$this->tokenProvider = $tokenProvider;
		$this->crypto = $crypto;
	}

	/**
	 * @param Registration $registration
	 */
	public function confirmEmail(Registration &$registration) {
		$registration->setEmailConfirmed(true);
		$this->registrationMapper->update($registration);
	}

	/**
	 * @param Registration $registration
	 */
	public function generateNewToken(Registration &$registration) {
		$this->registrationMapper->generateNewToken($registration);
		$this->registrationMapper->update($registration);
	}
	/**
	 * Create registration request, used by both the API and form
	 * @param string $email
	 * @param string $username
	 * @param string $password
	 * @param string $displayname
	 * @return Registration
	 */
	public function createRegistration($email, $username="", $password="", $displayname="") {
		$registration = new Registration();
		$registration->setEmail($email);
		$registration->setUsername($username);
		$registration->setDisplayname($displayname);
		if($password !== "") {
			$password = $this->crypto->encrypt($password);
			$registration->setPassword($password);
		}
		$this->registrationMapper->generateNewToken($registration);
		if ( $password !== '' && $username !== '' )
			$this->registrationMapper->generateClientSecret($registration);
		$this->registrationMapper->insert($registration);
		return $registration;
	}
        /**
         *  先锋号授权返回的userId 添加一个账号
         * @param type $userId
         * @param type $username
         * @param type $password
         * @param type $displayname
         * @return Registration
         */
	public function createUserId($userId, $username="", $password="", $displayname="") {
		$registration = new Registration();
		$registration->setEmail($userId);
		$registration->setUsername($username);
		$registration->setDisplayname($displayname);
		if($password !== "") {
			$password = $this->crypto->encrypt($password);
			$registration->setPassword($password);
		}
		$this->registrationMapper->generateNewToken($registration);
		if ( $password !== '' && $username !== '' )
			$this->registrationMapper->generateClientSecret($registration);
		$this->registrationMapper->insert($registration);
		return $registration;
	}
        /*更新授权表信息*/
        public function saveUserInfo(Registration &$registration, $username, $password, $displayname){
                    
            $password_enstr =  $this->DescBase64_Encrypt($password);
            $registration->setUsername($username);
            $registration->setPassword($password_enstr); 
            
            $registration->setDisplayname($displayname);
            //$this->registrationMapper->generateClientSecret($registration);
            $this->registrationMapper->update($registration);
        }                   
        
        public function makeRangePassd($length = 8){
            return  $this->random->generate($length, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS);
        }
        public function cleanClienToken(Registration &$registration){
            $registration->setClientSecret('NULL');
            $this->registrationMapper->update($registration); 
        }
        //保存token信息
        public function updateTokenInfo(Registration &$registration){
           // $registration->setClientSecret($tokeninfo);
            //$this->registrationMapper->update($registration);
            $this->registrationMapper->generateClientSecret($registration);
            $this->registrationMapper->update($registration);  
            return $registration->getClientSecret();
        }
        public function DescBase64_Encrypt($data){
            //$key =   $this->config->getSystemValue('instanceid');
            $key =   $this->config->getSystemValue('macnostr');
            $cipher = "DES-ECB";
            $iv = "";  
            $key =  $this->fill8(trim($key));      
                    
            return base64_encode( openssl_encrypt($data, $cipher, $key, OPENSSL_NO_PADDING, $iv));
        }
      /* desc base64 解密算法*/
        private function fill8($str){
            if(strlen($str) % 8){
                $len = strlen($str) + 8 - strlen($str) % 8; 
                $str = str_pad($str, $len, " ");
            }
            return $str;
        }
        /*解密 php openssl_decrypt 的加密 DescBase64_Encrypt 方法*/
        public function DesBase64_Decrypt($encrypt_str){
            $key =  $this->config->getSystemValue('macnostr');                    
            $encrypt_str = $this->fill8($encrypt_str);
            $key =  $this->fill8( trim($key) );
            $cipher = "DES-ECB";
            $iv = "";

            return    openssl_decrypt(base64_decode($encrypt_str), $cipher, $key ,OPENSSL_NO_PADDING, $iv) ;
                    
        }         
      
                    
	/**
	 * @param string $email
	 * @return Registration
	 * @throws RegistrationException
	 */
	public function validateEmail($email) {

		$this->mailService->validateEmail($email);

		// check for pending registrations
		try {
			return $this->registrationMapper->find($email);
		} catch (\Exception $e) {}

		if ( $this->userManager->getByEmail($email) ) {
			throw new RegistrationException(
				$this->l10n->t('A user has already taken this email, maybe you already have an account?'),
				$this->l10n->t('You can <a href="%s">log in now</a>.', [$this->urlGenerator->getAbsoluteURL('/')])
			);
		}

		if (!$this->checkAllowedDomains($email)) {
			throw new RegistrationException(
				$this->l10n->t(
					'Registration is only allowed for the following domains: ' .
					$this->config->getAppValue($this->appName, 'allowed_domains', '')
				)
			);
		}
		return null;
	}

	/**
	 * @param string $displayname
	 * @throws RegistrationException
	 */
	public function validateDisplayname($displayname) {
		if($displayname === "") {
			throw new RegistrationException($this->l10n->t('Please provide a valid display name.'));
		}
	}

	/**
	 * @param string $username
	 * @throws RegistrationException
	 */
	public function validateUsername($username) {
		if($username === "") {
			throw new RegistrationException($this->l10n->t('Please provide a valid user name.'));
		}

		if( $this->userManager->get($username) !== null) {
			throw new RegistrationException($this->l10n->t('The username you have chosen already exists.'));
		}
	}

	/**
	 * check if email domain is allowed
	 *
	 * @param string $email
	 * @return bool
	 */
	public function checkAllowedDomains($email) {
		$allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', '');
		if ( $allowed_domains !== '' ) {
			$allowed_domains = explode(';', $allowed_domains);
			$allowed = false;
			foreach ($allowed_domains as $domain) {
				$maildomain = explode("@", $email)[1];
				// valid domain, everythings fine
				if ($maildomain === $domain) {
					$allowed = true;
					break;
				}
			}
			return $allowed;
		}
		return true;
	}

	/**
	 * @return array
	 */
	public function getAllowedDomains() {
		$allowed_domains = $this->config->getAppValue($this->appName, 'allowed_domains', '');
		$allowed_domains = explode(';', $allowed_domains);
		return $allowed_domains;
	}
        
        public function getAuthorizeParams(){
            $default =  [
                'client_id' =>  '607323181031171338',
                 'secret_id' =>  'mg8pqcKAG597OF945PHP6385G2M30Iz1',
                 'appPackage' => 'http://testcloud1.xfplay.com:20081',
                 'authrize_url' =>  'https://cloud.xfplay.com',
            ];
            $request = \OC::$server->getRequest();
            $host = $request->getServerProtocol()."://".$request->getServerHost();
            $config =  [
                'client_id' => $this->config->getAppValue($this->appName, 'client_id'),
                 'secret_id' => $this->config->getAppValue($this->appName, 'secret_id'),
                 'appPackage' => $host,//$this->config->getAppValue($this->appName, 'appPackage'),
                 'authrize_url' => $this->config->getAppValue($this->appName, 'authrize_url'),
            ]; 
            foreach($config as $key => $val){
                if(empty($config[$key])){
                    $config[$key] = $default[$key];
                }
            }
            return $config;
        }

	/**
	 * Find registration entity for token
	 *
	 * @param string $token
	 * @return string
	 * @throws RegistrationException
	 */
	public function verifyToken($token) {
		try {
			return $this->registrationMapper->findByToken($token);
		} catch (DoesNotExistException $exception) {
			throw new RegistrationException($this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.', 404));
		}
	}
        /**
         * 查看是否注册过先锋号的userID
         * @param type $userId
         * @return type
         */
        public function verifyUserId($userId){
          try {   
            return $this->registrationMapper->find($userId);
          }catch (DoesNotExistException $exception) {
			throw new RegistrationException($this->l10n->t('Invalid verification URL. No registration request with this verification URL is found.', 404));
		}
		  
        }
        /**
         * 查看昵称是否在 registration 存在
         */
        public function checkUsernameIsexist($username){
           try {   
              $registration =  $this->registrationMapper->findByUsername($username);
              if(empty($registration)){
                  return false;
              }
              if($registration->getEmail()){
                  return true;
              }
            }catch (DoesNotExistException $exception) {
                return false;
	    }
            return false;
        }
        /**
         * 用户名已经绑定过
         * @param type $username
         * @return boolean
         */
        public function checkUsernameBinded($username){
            try{
                $user = $this->userManager->get($username);
                if(empty($user)){
                    return false;
                }
                if($user->getUID()){
                    return $user;
                }
            }catch(\Exception $e){
                 return false;
            }
           return false; 
        }
        /*检验云盘号密码是否正确*/
        public function verifyUserPassword($username, $password){
            try{
                $user = $this->userManager->checkPassword($username, $password);
                if(empty($user)){
                    return false;
                }
                if($user->getUID()){
                    return true;
                }
            }catch(\Exception $e){
                 return false;
            }
           return false;  
        }            
        /*如果账号存在重复，就按先锋号为主，修改对应的云盘号的密码*/
        public function setUserAccountPassword( $username, $password){
          try{
                $user = $this->userManager->get($username);
                if(empty($user)){
                    return false;
                }
                if($user->getUID()){
                    $user->setPassword($password);
                    return $user;
                }
            }catch(\Exception $e){
                 return false;
            }
            return false; 
        }
        /*授权绑定创建新账号*/
        public function bindCreateAccount(Registration &$registration, $username = null, $password = null){
            $this->validateUsername($username);
           
            $user = $this->userManager->createUser($username, $password);
		if ($user === false) {
			throw new RegistrationException($this->l10n->t('Unable to create user, there are problems with the user backend.'));
		}
		$userId = $user->getUID();
        
		// Add user to group
		$registered_user_group = $this->config->getAppValue($this->appName, 'registered_user_group', 'none');
		if ( $registered_user_group !== 'none' ) {
			try {
				$group = $this->groupManager->get($registered_user_group);
				$group->addUser($user);
				$groupId = $group->getGID();
			} catch (\Exception $e) {
				throw new RegistrationException($e->getMessage());
			}
		} else {
			$groupId = "";
		}

		// disable user if this is requested by config
		$admin_approval_required = $this->config->getAppValue($this->appName, 'admin_approval_required', "no");
		if ($admin_approval_required === "yes") {
			$user->setEnabled(false);
		}
 
		//$this->mailService->notifyAdmins($userId, $user->isEnabled(), $groupId);
		return $user;        
        }
	/**
	 * @param $registration
	 * @param string $username
	 * @param string $password
	 * @return \OCP\IUser
	 * @throws RegistrationException|\InvalidTokenException
	 */
	public function createAccount(Registration &$registration, $username = null, $password = null) {
		if($password === null && $registration->getPassword() === null) {
			$generatedPassword = $this->generateRandomDeviceToken();
			$registration->setPassword($this->crypto->encrypt($generatedPassword));
		}

		if ($username === null) {
			$username = $registration->getUsername();
		}

		if($registration->getPassword() !== null) {
			$password = $this->crypto->decrypt($registration->getPassword());
		}

		$this->validateUsername($username);

		/* TODO
		 * createUser tests username validity once, but validateUsername already checked it,
		 * but createUser doesn't check if there is a pending registration with that name
		 *
		 * And validateUsername will throw RegistrationException while
		 * createUser throws \InvalidTokenException in NC, \Exception in OC
		 */
		$user = $this->userManager->createUser($username, $password);
		if ($user === false) {
			throw new RegistrationException($this->l10n->t('Unable to create user, there are problems with the user backend.'));
		}
		$userId = $user->getUID();
		// Set user email
		try {
			$user->setEMailAddress($registration->getEmail());
		} catch (\Exception $e) {
			throw new RegistrationException($this->l10n->t('Unable to set user email: ' . $e->getMessage()));
		}

		// Add user to group
		$registered_user_group = $this->config->getAppValue($this->appName, 'registered_user_group', 'none');
		if ( $registered_user_group !== 'none' ) {
			try {
				$group = $this->groupManager->get($registered_user_group);
				$group->addUser($user);
				$groupId = $group->getGID();
			} catch (\Exception $e) {
				throw new RegistrationException($e->getMessage());
			}
		} else {
			$groupId = "";
		}

		// disable user if this is requested by config
		$admin_approval_required = $this->config->getAppValue($this->appName, 'admin_approval_required', "no");
		if ($admin_approval_required === "yes") {
			$user->setEnabled(false);
		}

		// Delete pending registration if no client secret is stored
		// with client secret implies registered via API
		// without client secret implies registered via form
		// if registered via API, the registration request will be deleted in apicontroller::status
		if($registration->getClientSecret() === null) {
			$res = $this->registrationMapper->delete($registration);
			if ($res === false) {
				throw new RegistrationException($this->l10n->t('Failed to delete pending registration request'));
			}
		}

		$this->mailService->notifyAdmins($userId, $user->isEnabled(), $groupId);
		return $user;
	}

	/**
	 * @param $token
	 * @return Registration
	 */
	public function getRegistrationForToken($token) {
		return $this->registrationMapper->findByToken($token);
	}

	/**
	 * @param $secret
	 * @return Registration
	 */
	public function getRegistrationForSecret($secret) {
		return $this->registrationMapper->findBySecret($secret);
	}

	/**
	 * @param Registration $registation
	 * @return null|\OCP\IUser
	 */
	public function getUserAccount(Registration &$registation) {
		$user = $this->userManager->get($registation->getUsername());
		return $user;
	}

	/**
	 * @param Registration $registration
	 */
	public function deleteRegistration(Registration $registration) {
		$this->registrationMapper->delete($registration);
	}

	/**
	 * Return a 25 digit device password
	 *
	 * Example: AbCdE-fGhIj-KlMnO-pQrSt-12345
	 *
	 * @return string
	 */
	private function generateRandomDeviceToken() {
		$groups = [];
		for ($i = 0; $i < 5; $i++) {
			$groups[] = $this->random->generate(5, ISecureRandom::CHAR_HUMAN_READABLE);
		}
		return implode('-', $groups);
	}

	/**
	 * @param string $uid
	 * @return string
	 * @throws RegistrationException
	 */
	public function generateAppPassword($uid) {
		$name = $this->l10n->t('Registration app auto setup');
		try {
			$sessionId = $this->session->getId();
		} catch (SessionNotAvailableException $ex) {
			throw new RegistrationException('Failed to generate an app token.');
		}

		try {
			$sessionToken = $this->tokenProvider->getToken($sessionId);
			$loginName = $sessionToken->getLoginName();
			try {
				$password = $this->tokenProvider->getPassword($sessionToken, $sessionId);
			} catch (PasswordlessTokenException $ex) {
				$password = null;
			}
		} catch (InvalidTokenException $ex) {
			throw new RegistrationException('Failed to generate an app token.');
		}

		$token = $this->generateRandomDeviceToken();
		$this->tokenProvider->generateToken($token, $uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN);
		return $token;
	}

	/**
	 * @param $userId
	 * @param $username
	 * @param $password
	 * @param $decrypt
	 * @return RedirectResponse|TemplateResponse
	 */
	public function loginUser($userId, $username, $password, $decrypt = false) {
		if ($decrypt) {
			$password = $this->crypto->decrypt($password);
		}
		if ( method_exists($this->usersession, 'createSessionToken') ) {
			$this->usersession->login($username, $password);
			$this->usersession->createSessionToken($this->request, $userId, $username, $password);
			return new RedirectResponse($this->urlGenerator->linkTo('', 'index.php'));
		} elseif (\OC_User::login($username, $password)) {
			$this->cleanupLoginTokens($userId);
			// FIXME unsetMagicInCookie will fail from session already closed, so now we always remember
			$logintoken = $this->random->generate(32);
			$this->config->setUserValue($userId, 'login_token', $logintoken, time());
			\OC_User::setMagicInCookie($userId, $logintoken);
            \OC_Util::redirectToDefaultPage();
		}
		// Render message in case redirect failed
		return new TemplateResponse('registration', 'message',
			['msg' => $this->l10n->t('Your account has been successfully created, you can <a href="%s">log in now</a>.'), [$this->urlGenerator->getAbsoluteURL('/')]]
			, 'guest'
		);

	}

	/**
	 * Replicates OC::cleanupLoginTokens() since it's protected
	 * @param string $userId
	 */
	public function cleanupLoginTokens($userId) {
		$cutoff = time() - $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
		$tokens = $this->config->getUserKeys($userId, 'login_token');
		foreach ($tokens as $token) {
			$time = $this->config->getUserValue($userId, 'login_token', $token);
			if ($time < $cutoff) {
				$this->config->deleteUserValue($userId, 'login_token', $token);
			}
		}
	}
        
        //Linux系统
        function getLinux(){
                    
            @exec("/home/xfplay_cloud/www/xfcloud/config/mac.sh", $result, $status);
            var_dump($result);                    
            return $result;
        }
        //Windows系统
        function getWindows(){
             $result = [];
                @exec("ipconfig /all", $result);
                if ( $result ) {
                    return $result;
                } else {
                    $ipconfig = $_SERVER["WINDIR"]."\system32\ipconfig.exe";
                    if(is_file($ipconfig)) {
                        @exec($ipconfig." /all", $result);
                    } else {
                        @exec($_SERVER["WINDIR"]."\system\ipconfig.exe /all", $result);
                        return $result;
                    }
                }
        }
        function getMac($OS){
            
            $result = [];
            $macAddr = [];
            switch ( strtolower($OS) ){
                    case "unix": break;
                    case "solaris": break;
                    case "aix": break;
                    case "linux":
                        $result = $this->getLinux();
                        break;
                    default: 
                        $result = $this->getWindows();
                        break;
            }
            $tem = array();
           
            foreach($result as $val){
                    
                if(preg_match("/[0-9a-f][0-9a-f][:-]"."[0-9a-f][0-9a-f][:-]"."[0-9a-f][0-9a-f][:-]"."[0-9a-f][0-9a-f][:-]"."[0-9a-f][0-9a-f][:-]"."[0-9a-f][0-9a-f]/i",$val,$tem) ){
                    $macAddr = $tem[0];//多个网卡时，会返回第一个网卡的mac地址，一般够用。
                    break;                    
                }
            } 
            return $macAddr;        
        }
}
