/** * @Route ("/impersonate") * @HttpMethod ({"GET"}) * * @param array $params * @throws Exception * @return string */ public function impersonate(array $params) { if (!Config::$a['allowImpersonation']) { throw new Exception('Impersonating is not allowed'); } $userId = isset($params['userId']) && !empty($params['userId']) ? $params['userId'] : ''; $username = isset($params['username']) && !empty($params['username']) ? $params['username'] : ''; if (empty($userId) && empty($username)) { throw new Exception('[username] or [userId] required'); } $authService = AuthenticationService::instance(); $userService = UserService::instance(); if (!empty($userId)) { $user = $userService->getUserById($userId); } else { if (!empty($username)) { $user = $userService->getUserByUsername($username); } } if (empty($user)) { throw new Exception('User not found. Try a different userId or username'); } $credentials = $authService->getUserCredentials($user, 'impersonating'); Session::start(); Session::updateCredentials($credentials); ChatIntegrationService::instance()->setChatSession($credentials, Session::getSessionId()); return 'redirect: /'; }
/** * @Route ("/logout") * * @return string */ public function logout() { if (Session::isStarted()) { ChatIntegrationService::instance()->deleteChatSession(Session::getSessionId()); Session::destroy(); } return 'redirect: /'; }
/** * @Route ("/admin/chat/broadcast") * @Secure ({"ADMIN"}) * * @param array $params * @param ViewModel $model * @throws Exception * @return string */ public function adminChatBroadcast(array $params, ViewModel $model) { $model->title = 'Chat'; FilterParams::required($params, 'message'); $chatIntegrationService = ChatIntegrationService::instance(); $chatIntegrationService->sendBroadcast($params['message']); Session::set('modelSuccess', sprintf('Sent broadcast: %s', $params['message'])); return 'redirect: /admin/chat'; }
private function sendResubscribeBroadcast(array $subscription) { $log = Application::instance()->getLogger(); $userService = UserService::instance(); $user = $userService->getUserById($subscription['userId']); if (!empty($user)) { try { // the subscription endDate has not been updated with the new subscription time $months = max(1, Date::getDateTime($subscription['createdDate'])->diff(Date::getDateTime($subscription['endDate']))->m); $months = $months > 1 ? $months . " months" : $months . " month"; $chatIntegrationService = ChatIntegrationService::instance(); $chatIntegrationService->sendBroadcast(sprintf("%s has resubscribed! Active for %s", $user['username'], $months)); } catch (\Exception $e) { $log->critical('Could not send resubscribe broadcast', $subscription); } } }
/** * Return true if there is a session cookie and its not empty * * @param START_IFCOOKIE|START_NOCOOKIE $flag * @return boolean */ public static function start($flag) { $session = self::instance(); if (!$session->isStarted()) { switch ($flag) { case self::START_IFCOOKIE: $sid = $session->getSessionCookie()->getCookie(); if (!empty($sid)) { ChatIntegrationService::instance()->renewChatSessionExpiration($sid); return $session->start(); } return false; case self::START_NOCOOKIE: return $session->start(); } } return false; }
/** * Checks the users current session status * Does a remember me login * @return void */ public function init() { $app = Application::instance(); $authService = AuthenticationService::instance(); // If the session hasnt started, or the data is not valid (result from php clearing the session data), check the Remember me cookie if (!Session::isStarted() || !Session::getCredentials()->isValid()) { $userId = $authService->getRememberMe(); if ($userId !== false) { $userManager = UserService::instance(); $user = $userManager->getUserById($userId); if (!empty($user)) { Session::start(Session::START_NOCOOKIE); $credentials = $authService->getUserCredentials($user, 'rememberme'); Session::updateCredentials($credentials); ChatIntegrationService::instance()->setChatSession($credentials, Session::getSessionId()); $authService->setRememberMe($user); } } } }
/** * @Route ("/subscription/process") * @Secure ({"USER"}) * * We were redirected here from PayPal after the buyer approved/cancelled the payment * * @param array $params * @return string * @throws Exception * @throws \Destiny\Common\Utils\FilterParamsException * TODO clean this method up */ public function subscriptionProcess(array $params) { FilterParams::required($params, 'subscriptionId'); FilterParams::required($params, 'token'); FilterParams::declared($params, 'success'); $userId = Session::getCredentials()->getUserId(); $userService = UserService::instance(); $ordersService = OrdersService::instance(); $subscriptionsService = SubscriptionsService::instance(); $payPalApiService = PayPalApiService::instance(); $chatIntegrationService = ChatIntegrationService::instance(); $authenticationService = AuthenticationService::instance(); $log = Application::instance()->getLogger(); $subscription = $subscriptionsService->getSubscriptionById($params['subscriptionId']); if (empty($subscription) || strcasecmp($subscription['status'], SubscriptionStatus::_NEW) !== 0) { throw new Exception('Invalid subscription record'); } try { $subscriptionType = $subscriptionsService->getSubscriptionType($subscription['subscriptionType']); $user = $userService->getUserById($subscription['userId']); if ($user['userId'] != $userId && $subscription['gifter'] != $userId) { throw new Exception('Invalid subscription'); } if ($params['success'] == '0' || $params['success'] == 'false' || $params['success'] === false) { throw new Exception('Order request failed'); } if (!$payPalApiService->retrieveCheckoutInfo($params['token'])) { throw new Exception('Failed to retrieve express checkout details'); } FilterParams::required($params, 'PayerID'); // if the order status is an error, the payerID is not returned Session::set('subscriptionId'); Session::set('token'); // Create the payment profile // Payment date is 1 day before subscription rolls over. if ($subscription['recurring'] == 1 || $subscription['recurring'] == true) { $startPaymentDate = Date::getDateTime(); $nextPaymentDate = Date::getDateTime(); $nextPaymentDate->modify('+' . $subscriptionType['billingFrequency'] . ' ' . strtolower($subscriptionType['billingPeriod'])); $nextPaymentDate->modify('-1 DAY'); $reference = $subscription['userId'] . '-' . $subscription['subscriptionId']; $paymentProfileId = $payPalApiService->createRecurringPaymentProfile($params['token'], $reference, $user['username'], $nextPaymentDate, $subscriptionType); if (empty($paymentProfileId)) { throw new Exception('Invalid recurring payment profileId returned from Paypal'); } $subscriptionsService->updateSubscription(array('subscriptionId' => $subscription['subscriptionId'], 'paymentStatus' => PaymentStatus::ACTIVE, 'paymentProfileId' => $paymentProfileId, 'billingStartDate' => $startPaymentDate->format('Y-m-d H:i:s'), 'billingNextDate' => $nextPaymentDate->format('Y-m-d H:i:s'))); } // Record the payments as well as check if any are not in the completed state // we put the subscription into "PENDING" state if a payment is found not completed $subscriptionStatus = SubscriptionStatus::ACTIVE; $DoECResponse = $payPalApiService->getECPaymentResponse($params['PayerID'], $params['token'], $subscriptionType['amount']); $payments = $payPalApiService->getResponsePayments($DoECResponse); foreach ($payments as $payment) { $payment['subscriptionId'] = $subscription['subscriptionId']; $payment['payerId'] = $params['PayerID']; $ordersService->addPayment($payment); // TODO: Payment provides no way of telling if the transaction with ALL payments was successful if ($payment['paymentStatus'] != PaymentStatus::COMPLETED) { $subscriptionStatus = SubscriptionStatus::PENDING; } } // Update subscription status $subscriptionsService->updateSubscription(array('subscriptionId' => $subscription['subscriptionId'], 'status' => $subscriptionStatus)); } catch (Exception $e) { $subscriptionsService->updateSubscription(array('subscriptionId' => $subscription['subscriptionId'], 'status' => SubscriptionStatus::ERROR)); $log->critical($e->getMessage(), $subscription); return 'redirect: /subscription/' . urlencode($subscription['subscriptionId']) . '/error'; } // only unban the user if the ban is non-permanent or the tier of the subscription is >= 2 // we unban the user if no ban is found because it also unmutes $ban = $userService->getUserActiveBan($user['userId']); if (empty($ban) or (!empty($ban['endtimestamp']) or $subscriptionType['tier'] >= 2)) { $chatIntegrationService->sendUnban($user['userId']); } // Broadcast $randomEmote = Config::$a['chat']['customemotes'][array_rand(Config::$a['chat']['customemotes'])]; if (!empty($subscription['gifter'])) { $gifter = $userService->getUserById($subscription['gifter']); $userName = $gifter['username']; $chatIntegrationService->sendBroadcast(sprintf("%s is now a %s subscriber! gifted by %s %s", $user['username'], $subscriptionType['tierLabel'], $gifter['username'], $randomEmote)); } else { $userName = $user['username']; $chatIntegrationService->sendBroadcast(sprintf("%s is now a %s subscriber! %s", $user['username'], $subscriptionType['tierLabel'], $randomEmote)); } $subMessage = Session::set('subMessage'); if (!empty($subMessage)) { $chatIntegrationService->sendBroadcast(sprintf("%s: %s", $userName, $subMessage)); } // Update the user $authenticationService->flagUserForUpdate($user['userId']); // Redirect to completion page return 'redirect: /subscription/' . urlencode($subscription['subscriptionId']) . '/complete'; }
/** * @Route ("/api/addtwitchsubscription") * @HttpMethod ({"POST"}) * * Expects the following POST variables: * privatekey=XXXXXXXX * * @param array $params * @return Response */ public function addSubscription(array $params) { $response = array(); // TODO GET RID OF THE COPY PASTE try { FilterParams::required($params, 'privatekey'); if (!$this->checkPrivateKey($params['privatekey'])) { throw new Exception('Invalid shared private key.'); } /* * The expected json schema is: {"123": 1, "431": 0} * where the key is the twitch user id and the value is whether * the user is a subscriber or not */ $data = json_decode(file_get_contents('php://input'), true); $userService = UserService::instance(); $authid = $userService->getTwitchIDFromNick($data['nick']); if ($authid) { $users = $userService->updateTwitchSubscriptions(array($authid => 1)); $chatIntegrationService = ChatIntegrationService::instance(); $authenticationService = AuthenticationService::instance(); foreach ($users as $user) { $authenticationService->flagUserForUpdate($user['userId']); if (!$user['istwitchsubscriber']) { // do not announce non-subs continue; } $chatIntegrationService->sendBroadcast(sprintf("%s is now a Twitch subscriber!", $user['username'])); } } $response = new Response(Http::STATUS_NO_CONTENT); } catch (Exception $e) { $response['success'] = false; $response['error'] = $e->getMessage(); $response = new Response(Http::STATUS_BAD_REQUEST, json_encode($response)); $response->addHeader(Http::HEADER_CONTENTTYPE, MimeType::JSON); } return $response; }
/** * @Route ("/admin/bans/purgeall") * @Secure ({"ADMIN"}) */ public function adminPurgeBans() { $chatService = ChatIntegrationService::instance(); $chatService->purgeBans(); return 'redirect: /admin/bans'; }
/** * @Route ("/subscription/process") * @Secure ({"USER"}) * @Transactional * * We were redirected here from PayPal after the buyer approved/cancelled the payment * * @param array $params */ public function subscriptionProcess(array $params, ViewModel $model) { FilterParams::isRequired($params, 'orderId'); FilterParams::isRequired($params, 'token'); FilterParams::isThere($params, 'success'); $ordersService = OrdersService::instance(); $userService = UserService::instance(); $subscriptionsService = SubscriptionsService::instance(); $payPalApiService = PayPalApiService::instance(); $chatIntegrationService = ChatIntegrationService::instance(); $authenticationService = AuthenticationService::instance(); $userId = Session::getCredentials()->getUserId(); // Get the order $order = $ordersService->getOrderByIdAndUserId($params['orderId'], $userId); if (empty($order) || strcasecmp($order['state'], OrderStatus::_NEW) !== 0) { throw new Exception('Invalid order record'); } try { // If we got a failed response URL if ($params['success'] == '0' || $params['success'] == 'false' || $params['success'] === false) { throw new Exception('Order request failed'); } // Get the subscription from the order $orderSubscription = $subscriptionsService->getSubscriptionByOrderId($order['orderId']); $subscriptionUser = $userService->getUserById($orderSubscription['userId']); // Make sure the subscription is valid if (empty($orderSubscription)) { throw new Exception('Invalid order subscription'); } // Make sure the subscription is either owned or gifted by the user if ($subscriptionUser['userId'] != $userId && $orderSubscription['gifter'] != $userId) { throw new Exception('Invalid order subscription'); } $subscriptionType = $subscriptionsService->getSubscriptionType($orderSubscription['subscriptionType']); $paymentProfile = $ordersService->getPaymentProfileByOrderId($order['orderId']); // Get the checkout info $ecResponse = $payPalApiService->retrieveCheckoutInfo($params['token']); if (!isset($ecResponse) || $ecResponse->Ack != 'Success') { throw new Exception('Failed to retrieve express checkout details'); } // Moved this down here, as if the order status is error, the payerID is not returned FilterParams::isRequired($params, 'PayerID'); // Point of no return - we only every want a person to get here if their order was a successful sequence Session::set('token'); Session::set('orderId'); // Recurring payment if (!empty($paymentProfile)) { $createRPProfileResponse = $payPalApiService->createRecurringPaymentProfile($paymentProfile, $params['token'], $subscriptionType); if (!isset($createRPProfileResponse) || $createRPProfileResponse->Ack != 'Success') { throw new Exception('Failed to create recurring payment request'); } $paymentProfileId = $createRPProfileResponse->CreateRecurringPaymentsProfileResponseDetails->ProfileID; $paymentStatus = $createRPProfileResponse->CreateRecurringPaymentsProfileResponseDetails->ProfileStatus; if (empty($paymentProfileId)) { throw new Exception('Invalid recurring payment profileId returned from Paypal'); } // Set the payment profile to active, and paymetProfileId $ordersService->updatePaymentProfileId($paymentProfile['profileId'], $paymentProfileId, $paymentStatus); // Update the payment profile $subscriptionsService->updateSubscriptionPaymentProfile($orderSubscription['subscriptionId'], $paymentProfile['profileId'], true); } // Complete the checkout $DoECResponse = $payPalApiService->getECPaymentResponse($params['PayerID'], $params['token'], $order); if (isset($DoECResponse) && $DoECResponse->Ack == 'Success') { if (isset($DoECResponse->DoExpressCheckoutPaymentResponseDetails->PaymentInfo)) { $payPalApiService->recordECPayments($DoECResponse, $params['PayerID'], $order); $ordersService->updateOrderState($order['orderId'], $order['state']); } else { throw new Exception('No payments for express checkout order'); } } else { throw new Exception($DoECResponse->Errors[0]->LongMessage); } // If the user already has a subscription and ONLY if this subscription was NOT a gift if (!isset($orderSubscription['gifter']) || empty($orderSubscription['gifter'])) { $activeSubscription = $subscriptionsService->getUserActiveSubscription($subscriptionUser['userId']); if (!empty($activeSubscription)) { // Cancel any attached payment profiles $ordersService = OrdersService::instance(); $paymentProfile = $ordersService->getPaymentProfileById($activeSubscription['paymentProfileId']); if (!empty($paymentProfile)) { $payPalApiService->cancelPaymentProfile($activeSubscription, $paymentProfile); $subscriptionsService->updateSubscriptionRecurring($activeSubscription['subscriptionId'], false); } // Cancel the active subscription $subscriptionsService->updateSubscriptionState($activeSubscription['subscriptionId'], SubscriptionStatus::CANCELLED); } } // Check if this is a gift, check that the giftee is still eligable if (!empty($orderSubscription['gifter']) && !$subscriptionsService->getCanUserReceiveGift($userId, $subscriptionUser['userId'])) { // Update the state to ERROR and log a critical error Application::instance()->getLogger()->critical('Duplicate subscription attempt, Gifter: %d GifteeId: %d, OrderId: %d', $userId, $subscriptionUser['userId'], $order['orderId']); $subscriptionsService->updateSubscriptionState($orderSubscription['subscriptionId'], SubscriptionStatus::ERROR); } else { // Unban the user if a ban is found $ban = $userService->getUserActiveBan($subscriptionUser['userId']); // only unban the user if the ban is non-permanent or the tier of the subscription is >= 2 // we unban the user if no ban is found because it also unmutes if (empty($ban) or (!empty($ban['endtimestamp']) or $orderSubscription['subscriptionTier'] >= 2)) { $chatIntegrationService->sendUnban($subscriptionUser['userId']); } // Activate the subscription (state) $subscriptionsService->updateSubscriptionState($orderSubscription['subscriptionId'], SubscriptionStatus::ACTIVE); // Flag the user for 'update' $authenticationService->flagUserForUpdate($subscriptionUser['userId']); // Random emote $randomEmote = Config::$a['chat']['customemotes'][array_rand(Config::$a['chat']['customemotes'])]; // Broadcast if (!empty($orderSubscription['gifter'])) { $gifter = $userService->getUserById($orderSubscription['gifter']); $userName = $gifter['username']; $chatIntegrationService->sendBroadcast(sprintf("%s is now a %s subscriber! gifted by %s %s", $subscriptionUser['username'], $subscriptionType['tierLabel'], $gifter['username'], $randomEmote)); } else { $userName = $subscriptionUser['username']; $chatIntegrationService->sendBroadcast(sprintf("%s is now a %s subscriber! %s", $subscriptionUser['username'], $subscriptionType['tierLabel'], $randomEmote)); } // Get the subscription message, and remove it from the session $subMessage = Session::set('subMessage'); if (!empty($subMessage)) { $chatIntegrationService->sendBroadcast(sprintf("%s: %s", $userName, $subMessage)); } } // Redirect to completion page return 'redirect: /subscription/' . urlencode($order['orderId']) . '/complete'; } catch (Exception $e) { if (!empty($order)) { $ordersService->updateOrderState($order['orderId'], OrderStatus::ERROR); } if (!empty($paymentProfile)) { $ordersService->updatePaymentStatus($paymentProfile['paymentId'], PaymentStatus::ERROR); } if (!empty($orderSubscription)) { $subscriptionsService->updateSubscriptionState($orderSubscription['subscriptionId'], SubscriptionStatus::ERROR); } $log = Application::instance()->getLogger(); $log->error($e->getMessage(), $order); return 'redirect: /subscription/' . urlencode($order['orderId']) . '/error'; } }
/** * Flag a user session for update * So that on their next request, the session data is updated. * Also does a chat session refresh * * @param int $userId */ public function flagUserForUpdate($userId) { $user = UserService::instance()->getUserById($userId); if (!empty($user)) { $cache = Application::instance()->getCacheDriver(); $cache->save(sprintf('refreshusersession-%s', $userId), time(), intval(ini_get('session.gc_maxlifetime'))); ChatIntegrationService::instance()->refreshChatUserSession($this->getUserCredentials($user, 'session')); } }
/** * Flag a user session for update * @param int $userId */ public function flagUserForUpdate($userId) { $user = UserService::instance()->getUserById($userId); $credentials = $this->getUserCredentials($user, 'session'); if (Session::instance() != null && Session::getCredentials()->getUserId() == $userId) { // Update the current session if the userId is the same as the credential user id Session::updateCredentials($credentials); // Init / create the current users chat session ChatIntegrationService::instance()->setChatSession($credentials, Session::getSessionId()); } else { // Otherwise set a session variable which is picked up by the remember me service to update the session $cache = Application::instance()->getCacheDriver(); $cache->save(sprintf('refreshusersession-%s', $userId), time(), intval(ini_get('session.gc_maxlifetime'))); } ChatIntegrationService::instance()->refreshChatUserSession($credentials); }
/** * @Route ("/profile/messages/send") * @Secure ({"USER"}) * @HttpMethod ({"POST"}) * * Expects the following GET|POST variables: * message=string * recipients[]=username|group * * @param array $params * @return Response */ public function sendMessage(array $params) { $privateMessageService = PrivateMessageService::instance(); $chatIntegrationService = ChatIntegrationService::instance(); $userService = UserService::instance(); $response = array('success' => false, 'message' => ''); try { FilterParams::required($params, 'message'); FilterParams::isarray($params, 'recipients'); $sessionCredentials = Session::getCredentials(); $userId = $sessionCredentials->getUserId(); $username = strtolower($sessionCredentials->getUsername()); $user = $userService->getUserById($userId); $recipients = array_unique(array_map('strtolower', $params['recipients'])); if (empty($recipients)) { throw new Exception('Invalid recipients list'); } if (count($recipients) === 1 && $recipients[0] == $username) { throw new Exception('Cannot send messages to yourself.'); } // Remove the user if its in the list $recipients = array_diff($recipients, array($username)); $ban = $userService->getUserActiveBan($userId); if (!empty($ban)) { throw new Exception("You cannot send messages while you are banned."); } $oldEnough = $userService->isUserOldEnough($userId); if (!$oldEnough) { throw new Exception("Your account is not old enough to send messages."); } // Because batch sending makes it difficult to run checks on each recipient // we only use the batch sending for admins e.g. sending to tiers etc. if (Session::hasRole(UserRole::ADMIN)) { $messages = $privateMessageService->batchAddMessage($userId, $params['message'], $params['recipients']); $chatIntegrationService->publishPrivateMessages($messages); } else { $recipients = $userService->getUserIdsByUsernames($params['recipients']); if (empty($recipients)) { throw new Exception('Invalid recipient value(s)'); } if (count($recipients) > 20) { throw new Exception('You may only send to maximum 20 users.'); } $credentials = new SessionCredentials($user); foreach ($recipients as $recipientId) { $canSend = $privateMessageService->canSend($credentials, $recipientId); if (!$canSend) { throw new Exception("You have sent too many messages, throttled."); } $targetuser = $userService->getUserById($recipientId); $message = array('userid' => $userId, 'targetuserid' => $recipientId, 'message' => $params['message'], 'isread' => 0); $message['id'] = $privateMessageService->addMessage($message); $chatIntegrationService->publishPrivateMessage(array('messageid' => $message['id'], 'message' => $message['message'], 'username' => $sessionCredentials->getUsername(), 'userid' => $userId, 'targetusername' => $targetuser['username'], 'targetuserid' => $targetuser['userId'])); } } $response['message'] = 'Message sent'; $response['success'] = true; } catch (\Exception $e) { $response['success'] = false; $response['message'] = $e->getMessage(); } $response = new Response(Http::STATUS_OK, json_encode($response)); $response->addHeader(Http::HEADER_CONTENTTYPE, MimeType::JSON); return $response; }
/** * @Route ("/admin/bans/purgeall") * @Secure ({"ADMIN"}) * * @param array $params * @throws Exception */ public function adminPurgeBans(array $params, ViewModel $model) { $chatService = ChatIntegrationService::instance(); $chatService->purgeBans(); return 'redirect: /admin/bans'; }