1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 16:31:33 +02:00
invoiceninja/app/Http/Controllers/BotController.php

356 lines
11 KiB
PHP
Raw Normal View History

2016-08-04 19:01:30 +02:00
<?php
namespace App\Http\Controllers;
2016-08-10 14:57:34 +02:00
use App\Libraries\CurlUtils;
2017-01-30 20:40:43 +01:00
use App\Libraries\Skype\SkypeResponse;
2016-08-13 21:19:37 +02:00
use App\Models\SecurityCode;
2017-01-30 20:40:43 +01:00
use App\Models\User;
2017-04-05 21:20:11 +02:00
use App\Models\Client;
2016-08-04 19:01:30 +02:00
use App\Ninja\Intents\BaseIntent;
2016-08-13 21:19:37 +02:00
use App\Ninja\Mailers\UserMailer;
2017-01-30 20:40:43 +01:00
use Auth;
use Cache;
use DB;
use Exception;
use Input;
use Utils;
2016-08-04 19:01:30 +02:00
class BotController extends Controller
{
2016-08-13 21:19:37 +02:00
protected $userMailer;
public function __construct(UserMailer $userMailer)
{
$this->userMailer = $userMailer;
}
2016-08-04 19:01:30 +02:00
public function handleMessage($platform)
{
2016-08-13 21:19:37 +02:00
$input = Input::all();
$botUserId = $input['from']['id'];
2016-08-10 19:48:24 +02:00
2017-01-30 17:05:31 +01:00
if (! $token = $this->authenticate($input)) {
2016-08-13 21:19:37 +02:00
return SkypeResponse::message(trans('texts.not_authorized'));
2016-08-10 19:48:24 +02:00
}
2016-08-13 21:19:37 +02:00
try {
if ($input['type'] === 'contactRelationUpdate') {
// brand new user, ask for their email
if ($input['action'] === 'add') {
$response = SkypeResponse::message(trans('texts.bot_get_email'));
$state = BOT_STATE_GET_EMAIL;
} elseif ($input['action'] === 'remove') {
$this->removeBot($botUserId);
$this->saveState($token, false);
2017-01-30 20:40:43 +01:00
2016-08-13 21:19:37 +02:00
return RESULT_SUCCESS;
}
} else {
$state = $this->loadState($token);
$text = strip_tags($input['text']);
2016-08-06 19:54:56 +02:00
2016-08-13 21:19:37 +02:00
// user gaves us their email
if ($state === BOT_STATE_GET_EMAIL) {
if ($this->validateEmail($text, $botUserId)) {
$response = SkypeResponse::message(trans('texts.bot_get_code'));
$state = BOT_STATE_GET_CODE;
} else {
$response = SkypeResponse::message(trans('texts.email_not_found', ['email' => $text]));
}
// user sent the scurity code
} elseif ($state === BOT_STATE_GET_CODE) {
if ($this->validateCode($text, $botUserId)) {
$response = SkypeResponse::message(trans('texts.bot_welcome') . trans('texts.bot_help_message'));
$state = BOT_STATE_READY;
} else {
$response = SkypeResponse::message(trans('texts.invalid_code'));
}
// regular chat message
} else {
2016-08-14 11:30:16 +02:00
if ($text === 'help') {
2016-08-13 21:19:37 +02:00
$response = SkypeResponse::message(trans('texts.bot_help_message'));
2016-08-14 11:30:16 +02:00
} elseif ($text == 'status') {
2016-08-13 21:19:37 +02:00
$response = SkypeResponse::message(trans('texts.intent_not_supported'));
} else {
2017-01-30 17:05:31 +01:00
if (! $user = User::whereBotUserId($botUserId)->with('account')->first()) {
2016-08-13 21:19:37 +02:00
return SkypeResponse::message(trans('texts.not_authorized'));
}
2016-08-07 21:42:32 +02:00
2016-08-13 21:19:37 +02:00
Auth::onceUsingId($user->id);
$user->account->loadLocalizationSettings();
2016-08-07 21:42:32 +02:00
2016-08-13 21:19:37 +02:00
$data = $this->parseMessage($text);
2017-04-04 11:54:38 +02:00
$intent = BaseIntent::createIntent($platform, $state, $data);
2016-08-13 21:19:37 +02:00
$response = $intent->process();
$state = $intent->getState();
}
}
}
2016-08-07 21:42:32 +02:00
$this->saveState($token, $state);
2016-08-07 08:10:36 +02:00
} catch (Exception $exception) {
2016-08-13 21:19:37 +02:00
$response = SkypeResponse::message($exception->getMessage());
2016-08-07 08:10:36 +02:00
}
2016-08-07 21:42:32 +02:00
2016-08-13 21:19:37 +02:00
$this->sendResponse($token, $botUserId, $response);
return RESULT_SUCCESS;
2016-08-07 21:42:32 +02:00
}
2017-04-04 11:54:38 +02:00
public function handleCommand()
{
$data = $this->parseMessage(request()->command);
2017-04-05 21:20:11 +02:00
// If they're viewing a client set it as the current state
$state = false;
$url = url()->previous();
preg_match('/clients\/(\d*)/', $url, $matches);
if (count($matches) >= 2) {
if ($client = Client::scope($matches[1])->first()) {
$state = BaseIntent::blankState();
$state->current->client = $client;
}
}
try {
$intent = BaseIntent::createIntent(BOT_PLATFORM_WEB_APP, $state, $data);
return $intent->process();
} catch (Exception $exception) {
$message = $exception->getMessage();
if (env('APP_DEBUG')) {
$message .= '<br/>' . request()->command . ' => ' . json_encode($data);
}
return redirect()->back()->withWarning($message);
}
2017-04-04 11:54:38 +02:00
}
2016-08-13 21:19:37 +02:00
private function authenticate($input)
2016-08-07 21:42:32 +02:00
{
2016-08-14 11:30:16 +02:00
$token = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : false;
2016-08-13 21:19:37 +02:00
if (Utils::isNinjaDev()) {
// skip validation for testing
2017-01-30 17:05:31 +01:00
} elseif (! $this->validateToken($token)) {
2016-08-13 21:19:37 +02:00
return false;
}
if ($token = Cache::get('msbot_token')) {
return $token;
}
2016-08-07 21:42:32 +02:00
$clientId = env('MSBOT_CLIENT_ID');
$clientSecret = env('MSBOT_CLIENT_SECRET');
$scope = 'https://graph.microsoft.com/.default';
$data = sprintf('grant_type=client_credentials&client_id=%s&client_secret=%s&scope=%s', $clientId, $clientSecret, $scope);
$response = CurlUtils::post(MSBOT_LOGIN_URL, $data);
$response = json_decode($response);
2016-08-13 21:19:37 +02:00
$expires = ($response->expires_in / 60) - 5;
Cache::put('msbot_token', $response->access_token, $expires);
2016-08-07 21:42:32 +02:00
return $response->access_token;
}
private function loadState($token)
{
$url = sprintf('%s/botstate/skype/conversations/%s', MSBOT_STATE_URL, '29:1C-OsU7OWBEDOYJhQUsDkYHmycOwOq9QOg5FVTwRX9ts');
$headers = [
2017-01-30 20:40:43 +01:00
'Authorization: Bearer ' . $token,
2016-08-07 21:42:32 +02:00
];
2016-08-07 08:10:36 +02:00
2016-08-07 21:42:32 +02:00
$response = CurlUtils::get($url, $headers);
$data = json_decode($response);
return json_decode($data->data);
2016-08-07 08:10:36 +02:00
}
private function parseMessage($message)
{
2016-08-04 19:01:30 +02:00
$appId = env('MSBOT_LUIS_APP_ID');
$subKey = env('MSBOT_LUIS_SUBSCRIPTION_KEY');
$message = rawurlencode($message);
2017-04-05 17:04:44 +02:00
$url = sprintf('%s/%s?subscription-key=%s&verbose=true&q=%s', MSBOT_LUIS_URL, $appId, $subKey, $message);
//$url = sprintf('%s?id=%s&subscription-key=%s&q=%s', MSBOT_LUIS_URL, $appId, $subKey, $message);
2016-08-04 19:01:30 +02:00
$data = file_get_contents($url);
$data = json_decode($data);
2016-08-10 19:48:24 +02:00
2016-08-07 08:10:36 +02:00
return $data;
2016-08-04 19:01:30 +02:00
}
2016-08-07 21:42:32 +02:00
private function saveState($token, $data)
2016-08-04 19:01:30 +02:00
{
2016-08-07 21:42:32 +02:00
$url = sprintf('%s/botstate/skype/conversations/%s', MSBOT_STATE_URL, '29:1C-OsU7OWBEDOYJhQUsDkYHmycOwOq9QOg5FVTwRX9ts');
2016-08-04 19:01:30 +02:00
2016-08-07 21:42:32 +02:00
$headers = [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
2016-08-04 19:01:30 +02:00
];
2016-08-13 21:19:37 +02:00
//echo "STATE<pre>" . htmlentities(json_encode($data), JSON_PRETTY_PRINT) . "</pre>";
2016-08-07 21:42:32 +02:00
$data = '{ eTag: "*", data: "' . addslashes(json_encode($data)) . '" }';
2016-08-06 19:54:56 +02:00
2016-08-10 14:57:34 +02:00
CurlUtils::post($url, $data, $headers);
2016-08-07 21:42:32 +02:00
}
2016-08-06 19:54:56 +02:00
2016-08-07 21:42:32 +02:00
private function sendResponse($token, $to, $message)
{
2016-08-04 19:01:30 +02:00
$url = sprintf('%s/conversations/%s/activities/', SKYPE_API_URL, $to);
2016-08-07 21:42:32 +02:00
$headers = [
'Authorization: Bearer ' . $token,
2016-08-04 19:01:30 +02:00
];
2016-08-13 21:19:37 +02:00
//echo "<pre>" . htmlentities(json_encode(json_decode($message), JSON_PRETTY_PRINT)) . "</pre>";
2016-08-07 21:42:32 +02:00
$response = CurlUtils::post($url, $message, $headers);
2016-08-04 19:01:30 +02:00
2016-08-13 21:19:37 +02:00
//var_dump($response);
}
private function validateEmail($email, $botUserId)
{
2017-01-30 17:05:31 +01:00
if (! $email || ! $botUserId) {
2016-08-13 21:19:37 +02:00
return false;
}
// delete any expired codes
SecurityCode::whereBotUserId($botUserId)
->where('created_at', '<', DB::raw('now() - INTERVAL 10 MINUTE'))
->delete();
if (SecurityCode::whereBotUserId($botUserId)->first()) {
return false;
}
$user = User::whereEmail($email)
->whereNull('bot_user_id')
->first();
2017-01-30 17:05:31 +01:00
if (! $user) {
2016-08-13 21:19:37 +02:00
return false;
}
$code = new SecurityCode();
$code->user_id = $user->id;
$code->account_id = $user->account_id;
$code->code = mt_rand(100000, 999999);
$code->bot_user_id = $botUserId;
$code->save();
$this->userMailer->sendSecurityCode($user, $code->code);
return $code->code;
}
private function validateCode($input, $botUserId)
{
2017-01-30 17:05:31 +01:00
if (! $input || ! $botUserId) {
2016-08-13 21:19:37 +02:00
return false;
}
$code = SecurityCode::whereBotUserId($botUserId)
->where('created_at', '>', DB::raw('now() - INTERVAL 10 MINUTE'))
->where('attempts', '<', 5)
->first();
2017-01-30 17:05:31 +01:00
if (! $code) {
2016-08-13 21:19:37 +02:00
return false;
}
2017-01-30 17:05:31 +01:00
if (! hash_equals($code->code, $input)) {
2016-08-13 21:19:37 +02:00
$code->attempts += 1;
$code->save();
2017-01-30 20:40:43 +01:00
2016-08-13 21:19:37 +02:00
return false;
}
$user = User::find($code->user_id);
$user->bot_user_id = $code->bot_user_id;
$user->save();
return true;
}
private function removeBot($botUserId)
{
$user = User::whereBotUserId($botUserId)->first();
$user->bot_user_id = null;
$user->save();
2016-08-04 19:01:30 +02:00
}
2016-08-10 19:48:24 +02:00
private function validateToken($token)
{
2017-01-30 17:05:31 +01:00
if (! $token) {
2016-08-10 19:48:24 +02:00
return false;
}
2016-08-14 11:30:16 +02:00
$token = explode(' ', $token)[1];
2016-08-10 19:48:24 +02:00
// https://blogs.msdn.microsoft.com/tsmatsuz/2016/07/12/developing-skype-bot/
// 0:Invalid, 1:Valid
$token_valid = 0;
// 1 separate token by dot (.)
$token_arr = explode('.', $token);
$headers_enc = $token_arr[0];
$claims_enc = $token_arr[1];
$sig_enc = $token_arr[2];
// 2 base 64 url decoding
2017-01-30 17:05:31 +01:00
$headers_arr = json_decode($this->base64_url_decode($headers_enc), true);
$claims_arr = json_decode($this->base64_url_decode($claims_enc), true);
2016-08-10 19:48:24 +02:00
$sig = $this->base64_url_decode($sig_enc);
// 3 get key list
$keylist = file_get_contents('https://api.aps.skype.com/v1/keys');
2017-01-30 17:05:31 +01:00
$keylist_arr = json_decode($keylist, true);
foreach ($keylist_arr['keys'] as $key => $value) {
2016-08-10 19:48:24 +02:00
// 4 select one key (which matches)
2017-01-30 17:05:31 +01:00
if ($value['kid'] == $headers_arr['kid']) {
2016-08-10 19:48:24 +02:00
// 5 get public key from key info
$cert_txt = '-----BEGIN CERTIFICATE-----' . "\n" . chunk_split($value['x5c'][0], 64) . '-----END CERTIFICATE-----';
$cert_obj = openssl_x509_read($cert_txt);
$pkey_obj = openssl_pkey_get_public($cert_obj);
$pkey_arr = openssl_pkey_get_details($pkey_obj);
$pkey_txt = $pkey_arr['key'];
// 6 verify signature
$token_valid = openssl_verify($headers_enc . '.' . $claims_enc, $sig, $pkey_txt, OPENSSL_ALGO_SHA256);
}
}
// 7 show result
2017-01-30 20:40:43 +01:00
return $token_valid == 1;
2016-08-10 19:48:24 +02:00
}
2017-01-30 17:05:31 +01:00
private function base64_url_decode($arg)
{
2016-08-10 19:48:24 +02:00
$res = $arg;
$res = str_replace('-', '+', $res);
$res = str_replace('_', '/', $res);
switch (strlen($res) % 4) {
case 0:
break;
case 2:
2017-01-30 20:40:43 +01:00
$res .= '==';
2016-08-10 19:48:24 +02:00
break;
case 3:
2017-01-30 20:40:43 +01:00
$res .= '=';
2016-08-10 19:48:24 +02:00
break;
default:
break;
}
$res = base64_decode($res);
2017-01-30 20:40:43 +01:00
2016-08-10 19:48:24 +02:00
return $res;
}
2016-08-04 19:01:30 +02:00
}