diff --git a/airtime_mvc/application/configs/ACL.php b/airtime_mvc/application/configs/ACL.php index b52904b7a..fe0c5b425 100644 --- a/airtime_mvc/application/configs/ACL.php +++ b/airtime_mvc/application/configs/ACL.php @@ -7,13 +7,15 @@ $ccAcl = new Zend_Acl(); $ccAcl->addRole(new Zend_Acl_Role('G')) ->addRole(new Zend_Acl_Role('H'), 'G') ->addRole(new Zend_Acl_Role('P'), 'H') - ->addRole(new Zend_Acl_Role('A'), 'P'); + ->addRole(new Zend_Acl_Role('A'), 'P') + ->addRole(new Zend_Acl_Role('S'), 'A'); $ccAcl->add(new Zend_Acl_Resource('library')) ->add(new Zend_Acl_Resource('index')) ->add(new Zend_Acl_Resource('user')) ->add(new Zend_Acl_Resource('error')) ->add(new Zend_Acl_Resource('login')) + ->add(new Zend_Acl_Resource('whmcs-login')) ->add(new Zend_Acl_Resource('playlist')) ->add(new Zend_Acl_Resource('plupload')) ->add(new Zend_Acl_Resource('schedule')) @@ -35,6 +37,7 @@ $ccAcl->add(new Zend_Acl_Resource('library')) /** Creating permissions */ $ccAcl->allow('G', 'index') ->allow('G', 'login') + ->allow('G', 'whmcs-login') ->allow('G', 'error') ->allow('G', 'user', 'edit-user') ->allow('G', 'showbuilder') diff --git a/airtime_mvc/application/configs/constants.php b/airtime_mvc/application/configs/constants.php index 34f8cab41..9e2563d0f 100644 --- a/airtime_mvc/application/configs/constants.php +++ b/airtime_mvc/application/configs/constants.php @@ -41,6 +41,7 @@ define('UI_MDATA_VALUE_FORMAT_STREAM' , 'live stream'); //User types define('UTYPE_HOST' , 'H'); define('UTYPE_ADMIN' , 'A'); +define('UTYPE_SUPERADMIN' , 'S'); define('UTYPE_GUEST' , 'G'); define('UTYPE_PROGRAM_MANAGER' , 'P'); diff --git a/airtime_mvc/application/controllers/LoginController.php b/airtime_mvc/application/controllers/LoginController.php index ee4ced5e4..0ddb17d2d 100644 --- a/airtime_mvc/application/controllers/LoginController.php +++ b/airtime_mvc/application/controllers/LoginController.php @@ -1,5 +1,7 @@ getRequest(); + //Allow AJAX requests from www.airtime.pro. We use this to automatically login users + //after they sign up from the microsite. + $response = $this->getResponse()->setHeader('Access-Control-Allow-Origin', '*'); + $origin = $request->getHeader('Origin'); + if (($origin != "") && (!in_array($origin, array("http://www.airtime.pro", "https://www.airtime.pro")))) + { + //Don't allow CORS from other domains to prevent XSS. + throw new Zend_Controller_Action_Exception('Forbidden', 403); + } + Application_Model_Locale::configureLocalization($request->getcookie('airtime_locale', 'en_CA')); if (Zend_Auth::getInstance()->hasIdentity()) { - $this->_redirect('Showbuilder'); } @@ -74,11 +85,23 @@ class LoginController extends Zend_Controller_Action $this->_redirect('Showbuilder'); } else { - $message = _("Wrong username or password provided. Please try again."); - Application_Model_Subjects::increaseLoginAttempts($username); - Application_Model_LoginAttempts::increaseAttempts($_SERVER['REMOTE_ADDR']); - $form = new Application_Form_Login(); - $error = true; + $email = $form->getValue('username'); + $authAdapter = new WHMCS_Auth_Adapter("admin", $email, $password); + $auth = Zend_Auth::getInstance(); + $result = $auth->authenticate($authAdapter); + if ($result->isValid()) { + //set the user locale in case user changed it in when logging in + Application_Model_Preference::SetUserLocale($locale); + + $this->_redirect('Showbuilder'); + } + else { + $message = _("Wrong username or password provided. Please try again."); + Application_Model_Subjects::increaseLoginAttempts($username); + Application_Model_LoginAttempts::increaseAttempts($_SERVER['REMOTE_ADDR']); + $form = new Application_Form_Login(); + $error = true; + } } } } diff --git a/airtime_mvc/application/controllers/UserController.php b/airtime_mvc/application/controllers/UserController.php index fad0277db..f28917e9e 100644 --- a/airtime_mvc/application/controllers/UserController.php +++ b/airtime_mvc/application/controllers/UserController.php @@ -62,7 +62,11 @@ class UserController extends Zend_Controller_Action if ($formData['password'] != "xxxxxx") { $user->setPassword($formData['password']); } - $user->setType($formData['type']); + if (array_key_exists('type', $formData)) { + if ($formData['type'] != UTYPE_SUPERADMIN) { //Don't allow any other user to be promoted to Super Admin + $user->setType($formData['type']); + } + } $user->setEmail($formData['email']); $user->setCellPhone($formData['cell_phone']); $user->setSkype($formData['skype']); @@ -187,6 +191,12 @@ class UserController extends Zend_Controller_Action } $user = new Application_Model_User($delId); + + // Don't allow super admins to be deleted. + if ($user->isSuperAdmin()) + { + return; + } # Take care of the user's files by either assigning them to somebody # or deleting them all diff --git a/airtime_mvc/application/controllers/WhmcsLoginController.php b/airtime_mvc/application/controllers/WhmcsLoginController.php new file mode 100644 index 000000000..0f0e327de --- /dev/null +++ b/airtime_mvc/application/controllers/WhmcsLoginController.php @@ -0,0 +1,261 @@ +getRequest(); + $this->view->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $username = "admin"; //This is just for appearance in your session. It shows up in the corner of the Airtime UI. + $email = $_POST["email"]; + $password = $_POST["password"]; + + Application_Model_Locale::configureLocalization($request->getcookie('airtime_locale', 'en_CA')); + if (Zend_Auth::getInstance()->hasIdentity()) + { + $this->_redirect('Showbuilder'); + } + + $authAdapter = new WHMCS_Auth_Adapter($username, $email, $password); + + $auth = Zend_Auth::getInstance(); + $result = $auth->authenticate($authAdapter); + if ($result->isValid()) { + //all info about this user from the login table omit only the password + //$userInfo = $authAdapter->getResultRowObject(null, 'password'); + + //the default storage is a session with namespace Zend_Auth + /* + [id] => 1 + [login] => admin + [pass] => hashed password + [type] => A + [first_name] => + [last_name] => + [lastlogin] => + [lastfail] => + [skype_contact] => + [jabber_contact] => + [email] => asdfasdf@asdasdf.com + [cell_phone] => + [login_attempts] => 0 + */ + + //Zend_Auth already does this for us, it's not needed: + //$authStorage = $auth->getStorage(); + //$authStorage->write($result->getIdentity()); //$userInfo); + + //set the user locale in case user changed it in when logging in + //$locale = $form->getValue('locale'); + //Application_Model_Preference::SetUserLocale($locale); + + $this->_redirect('Showbuilder'); + } + else { + echo("Sorry, that username or password was incorrect."); + } + + return; + } +} + +class WHMCS_Auth_Adapter implements Zend_Auth_Adapter_Interface { + private $username; + private $password; + private $email; + + function __construct($username, $email, $password) { + $this->username = $username; + $this->password = $password; + $this->email = $email; + $this->identity = null; + } + + function authenticate() { + list($credentialsValid, $clientId) = $this->validateCredentialsWithWHMCS($this->email, $this->password); + if (!$credentialsValid) + { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, null); + } + if (!$this->verifyClientSubdomainOwnership($clientId)) + { + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, null); + } + + $identity = array(); + + //TODO: Get identity of the first admin user! + + /* + $identity["id"] = 1; + $identity["type"] = "S"; + $identity["login"] = $this->username; //admin"; + $identity["email"] = $this->email;*/ + $identity = $this->getSuperAdminIdentity(); + if (is_null($identity)) { + Logging::error("No super admin user found"); + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null); + } + $identity = (object)$identity; //Convert the array into an stdClass object + + try { + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $identity); + } catch (Exception $e) { + // exception occured + return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null); + } + } + + private function getSuperAdminIdentity() + { + $firstSuperAdminUser = CcSubjsQuery::create() + ->filterByDbType('S') + ->orderByDbId() + ->findOne(); + if (!$firstSuperAdminUser) { + //If there's no super admin users, get the first regular admin user! + $firstSuperAdminUser = CcSubjsQuery::create() + ->filterByDbType('A') + ->orderByDbId() + ->findOne(); + if (!$firstSuperAdminUser) { + return null; + } + } + $identity["id"] = $firstSuperAdminUser->getDbId(); + $identity["type"] = "S"; //Super Admin + $identity["login"] = $firstSuperAdminUser->getDbLogin(); + $identity["email"] = $this->email; + return $identity; + } + + //Returns an array! Read the code carefully: + private function validateCredentialsWithWHMCS($email, $password) + { + $client_postfields = array(); + $client_postfields["username"] = $_SERVER['WHMCS_USERNAME']; //WHMCS API username + $client_postfields["password"] = md5($_SERVER['WHMCS_PASSWORD']); //WHMCS API password + $client_postfields["action"] ="validatelogin"; + $client_postfields["responsetype"] = "json"; + + $client_postfields["email"] = $email; + $client_postfields["password2"] = $password; + + $query_string = ""; + foreach ($client_postfields as $k => $v) $query_string .= "$k=".urlencode($v)."&"; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, WHMCS_API_URL); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_FAILONERROR, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + $jsondata = curl_exec($ch); + if (curl_error($ch)) { + die(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); + //die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch)); + } + curl_close($ch); + + $arr = json_decode($jsondata, true); # Decode JSON String + + if ($arr["result"] != "success") { + return array(false, -1); + } + $clientId = $arr["userid"]; + + return array(true, $clientId); + } + + function verifyClientSubdomainOwnership($clientId) + { + //Do a quick safety check to ensure the client ID we're authenticating + //matches up to the owner of this instance. + if ($clientId != Application_Model_Preference::GetClientId()) + { + return false; + } + $client_postfields = array(); + $client_postfields["username"] = $_SERVER['WHMCS_USERNAME']; + $client_postfields["password"] = md5($_SERVER['WHMCS_PASSWORD']); + $client_postfields["action"] ="getclientsproducts"; + $client_postfields["responsetype"] = "json"; + + $client_postfields["clientid"] = $clientId; + //$client_postfields["stats"] = "true"; + + $query_string = ""; + foreach ($client_postfields as $k => $v) $query_string .= "$k=".urlencode($v)."&"; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, WHMCS_API_URL); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_FAILONERROR, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + $jsondata = curl_exec($ch); + if (curl_error($ch)) { + die(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)); + //die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch)); + } + curl_close($ch); + + $arr = json_decode($jsondata, true); # Decode JSON String + //$client_id = $arr["clientid"]; + //print_r($arr); + if ($arr["result"] != "success") { + die("Sorry, that email address or password was incorrect."); + } + + $doesAirtimeProductExist = false; + $isAirtimeAccountSuspended = true; + $airtimeProduct = null; + + foreach ($arr["products"] as $product) + { + if (strpos($product[0]["groupname"], "Airtime") === FALSE) + { + //Ignore non-Airtime products + continue; + } + else + { + if ($product[0]["status"] === "Active") { + $airtimeProduct = $product[0]; + $subdomain = ''; + + foreach ($airtimeProduct['customfields']['customfield'] as $customField) + { + if ($customField['name'] === SUBDOMAIN_WHMCS_CUSTOM_FIELD_NAME) + { + $subdomain = $customField['value']; + if (($subdomain . ".airtime.pro") === $_SERVER['SERVER_NAME']) + { + return true; + } + } + } + } + } + } + return false; + } +} \ No newline at end of file diff --git a/airtime_mvc/application/controllers/plugins/Acl_plugin.php b/airtime_mvc/application/controllers/plugins/Acl_plugin.php index c5dc4b9f4..bfacdeb09 100644 --- a/airtime_mvc/application/controllers/plugins/Acl_plugin.php +++ b/airtime_mvc/application/controllers/plugins/Acl_plugin.php @@ -117,8 +117,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract return; } - if (in_array($controller, array("api", "auth", "locale", "upgrade"))) { - + if (in_array($controller, array("api", "auth", "locale", "upgrade", 'whmcs-login'))) { $this->setRoleName("G"); } elseif (!Zend_Auth::getInstance()->hasIdentity()) { diff --git a/airtime_mvc/application/forms/AddUser.php b/airtime_mvc/application/forms/AddUser.php index 1d3835ae7..09d5216c1 100644 --- a/airtime_mvc/application/forms/AddUser.php +++ b/airtime_mvc/application/forms/AddUser.php @@ -95,9 +95,9 @@ class Application_Form_AddUser extends Zend_Form "G" => _("Guest"), "H" => _("DJ"), "P" => _("Program Manager"), - "A" => _("Admin") + "A" => _("Admin"), )); - $select->setRequired(true); + $select->setRequired(false); $this->addElement($select); $saveBtn = new Zend_Form_Element_Button('save_user'); diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php index cfed23b1c..24d61e066 100644 --- a/airtime_mvc/application/models/Preference.php +++ b/airtime_mvc/application/models/Preference.php @@ -6,7 +6,7 @@ class Application_Model_Preference { private static function getUserId() - { + { //pass in true so the check is made with the autoloader //we need this check because saas calls this function from outside Zend if (!class_exists("Zend_Auth", true) || !Zend_Auth::getInstance()->hasIdentity()) { diff --git a/airtime_mvc/application/models/User.php b/airtime_mvc/application/models/User.php index 8a1008577..dc1cd49c8 100644 --- a/airtime_mvc/application/models/User.php +++ b/airtime_mvc/application/models/User.php @@ -49,7 +49,12 @@ class Application_Model_User { return $this->isUserType(UTYPE_ADMIN); } - + + public function isSuperAdmin() + { + return $this->isUserType(UTYPE_SUPERADMIN); + } + public function canSchedule($p_showId) { $type = $this->getType(); diff --git a/airtime_mvc/application/views/scripts/form/edit-user.phtml b/airtime_mvc/application/views/scripts/form/edit-user.phtml index 1cf55dba4..b26d87499 100644 --- a/airtime_mvc/application/views/scripts/form/edit-user.phtml +++ b/airtime_mvc/application/views/scripts/form/edit-user.phtml @@ -157,7 +157,7 @@ - + diff --git a/airtime_mvc/application/views/scripts/user/add-user.phtml b/airtime_mvc/application/views/scripts/user/add-user.phtml index a85744a46..b58607a89 100644 --- a/airtime_mvc/application/views/scripts/user/add-user.phtml +++ b/airtime_mvc/application/views/scripts/user/add-user.phtml @@ -26,6 +26,9 @@ +
successMessage ?>
diff --git a/airtime_mvc/public/js/airtime/user/user.js b/airtime_mvc/public/js/airtime/user/user.js index 71f9eed6e..f3a414b66 100644 --- a/airtime_mvc/public/js/airtime/user/user.js +++ b/airtime_mvc/public/js/airtime/user/user.js @@ -4,6 +4,17 @@ function populateForm(entries){ $('.errors').remove(); $('.success').remove(); + if (entries.type === 'S') + { + $("#user_details").hide(); + $("#user_details_superadmin_message").show(); + $('#type').attr('disabled', '1'); + } else { + $("#user_details").show(); + $("#user_details_superadmin_message").hide(); + $('#type').removeAttr('disabled'); + } + $('#user_id').val(entries.id); $('#login').val(entries.login); $('#first_name').val(entries.first_name); @@ -57,6 +68,10 @@ function rowCallback( nRow, aData, iDisplayIndex ){ } else if ( aData['type'] == "P" ) { $('td:eq(3)', nRow).html( $.i18n._('Program Manager') ); + } else if ( aData['type'] == "S" ) + { + $('td:eq(3)', nRow).html( $.i18n._('Super Admin') ); + $('td:eq(4)', nRow).html(""); //Disable deleting the super admin } return nRow; @@ -183,7 +198,7 @@ $(document).ready(function() { var newUser = {login:"", first_name:"", last_name:"", type:"G", id:""}; - $('#add_user_button').live('click', function(){populateForm(newUser)}); + $('#add_user_button').live('click', function(){populateForm(newUser);}); $('#save_user').live('click', function(){ var data = $('#user_form').serialize();