Files
wevads-platform/app/controllers/HuaweiAccounts.php
2026-02-26 03:06:17 +01:00

856 lines
29 KiB
PHP
Executable File

<?php declare(strict_types=1); namespace IR\App\Controllers; if (!defined('IR_START')) exit('<pre>No direct script access allowed</pre>');
/**
* @framework Wevads Framework
* @version 1.0
* @author Amine Idrissi <contact@iresponse.tech>
* @date 2018
* @name HuaweiAccounts.php
*/
# core
use IR\Core\Application as Application;
# mvc
use IR\Mvc\Controller as Controller;
# helpers
use IR\App\Helpers\Authentication as Authentication;
use IR\App\Helpers\Page as Page;
use IR\App\Helpers\DataTable as DataTable;
use IR\App\Helpers\Permissions as Permissions;
# http
use IR\Http\Request as Request;
# models
use \IR\App\Models\Admin\HuaweiAccount as HuaweiAccount;
# exceptions
use IR\Exceptions\Types\PageException as PageException;
/**
* @name HuaweiAccounts
* @description HuaweiAccounts Controller
*/
class HuaweiAccounts extends Controller
{
/**
* @app
* @readwrite
*/
protected $app;
/**
* @app
* @readwrite
*/
protected $authenticatedUser;
/**
* @name init
* @description initializing process before the action method executed
* @once
* @protected
*/
public function init()
{
# set the current application to a local variable
$this->app = Application::getCurrent();
# connect to the database
$this->app->database('system')->connect();
# check for authentication
if(!Authentication::isUserAuthenticated())
{
Page::redirect($this->app->http->request->getBaseURL() . RDS . 'auth' . RDS . 'login.' . DEFAULT_EXTENSION);
}
# check users roles
Authentication::checkUserRoles();
# get the authenticated user
$this->authenticatedUser = Authentication::getAuthenticatedUser();
}
/**
* @name main
* @description the main action
* @before init
* @after closeConnections,checkForMessage
*/
public function main()
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,__FUNCTION__);
if($access == false)
{
throw new PageException('Access Denied !',403);
}
# preparing the columns array to create the list
$columnsArray = [
'id',
'name',
'status',
'access_key',
'region',
'created_by',
'created_date'
];
# creating the html part of the list
$columns = Page::createTableHeader($columnsArray);
$filters = Page::createTableFilters($columnsArray);
# set menu status
$this->masterView->set([
'cloud_management' => 'true',
'huawei_management' => 'true',
'huawei_accounts' => 'true',
'huawei_accounts_show' => 'true'
]);
# set data to the page view
$this->pageView->set([
'columns' => $columns,
'filters' => $filters
]);
}
/**
* @name get
* @description the get action
* @before init
* @after closeConnections
*/
public function get()
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,'main');
if($access == false)
{
throw new PageException('Access Denied !',403);
}
# get post data
$data = $this->app->http->request->retrieve(Request::ALL,Request::POST);
if(count($data))
{
# preparing the columns array to create the list
$columns = [
'id',
'name',
'status',
'access_key',
'region',
'created_by',
'created_date'
];
# fetching the results to create the ajax list
die(json_encode(DataTable::init($data,'admin.huawei_accounts',$columns,new HuaweiAccount(),'huawei-accounts','ASC')));
}
}
/**
* @name add
* @description the add action
* @before init
* @after closeConnections,checkForMessage
*/
public function add()
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,__FUNCTION__);
if($access == false)
{
throw new PageException('Access Denied !',403);
}
# set menu status
$this->masterView->set([
'cloud_management' => 'true',
'huawei_management' => 'true',
'huawei_accounts' => 'true',
'huawei_accounts_add' => 'true'
]);
}
/**
* @name edit
* @description the edit action
* @before init
* @after closeConnections,checkForMessage
*/
public function edit()
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,__FUNCTION__);
if($access == false)
{
throw new PageException('Access Denied !',403);
}
$arguments = func_get_args();
$id = isset($arguments) && count($arguments) > 0 ? $arguments[0] : null;
$valid = true;
# set menu status
$this->masterView->set([
'cloud_management' => 'true',
'huawei_management' => 'true',
'huawei_accounts' => 'true',
'huawei_accounts_show' => 'true'
]);
if(!isset($id) || !is_numeric($id) || intval($id) == 0)
{
$valid = false;
}
$account = HuaweiAccount::first(HuaweiAccount::FETCH_ARRAY,['id = ?',$id]);
if(count($account) == 0)
{
$valid = false;
}
if($valid == true)
{
# set data to the page view
$this->pageView->set([
'account' => $account
]);
}
else
{
# stores the message in the session
Page::registerMessage('error','Invalid digital ocean account id !');
# redirect to lists page
Page::redirect();
}
}
/**
* @name testConnection
* @description test connection to Huawei Cloud API
* @before init
* @after closeConnections
*/
public function testConnection()
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,'main');
if($access == false)
{
Page::printApiResults(403,'Access Denied !');
}
# get post data
$data = $this->app->http->request->retrieve(Request::ALL,Request::POST);
if(!isset($data['account-id']) || !is_numeric($data['account-id']))
{
Page::printApiResults(400,'Invalid account ID !');
}
$accountId = intval($data['account-id']);
$account = HuaweiAccount::first(HuaweiAccount::FETCH_ARRAY,['id = ?',$accountId]);
if(count($account) == 0)
{
Page::printApiResults(404,'Account not found !');
}
try
{
# Test Huawei Cloud API connection
$result = $this->testHuaweiConnection($account);
if($result['success'])
{
Page::printApiResults(200,'Connection successful !', ['status' => 'connected']);
}
else
{
Page::printApiResults(400,'Connection failed: ' . $result['error'], ['status' => 'failed']);
}
}
catch(Exception $e)
{
Page::printApiResults(500,'Connection test error: ' . $e->getMessage(), ['status' => 'error']);
}
}
/**
* @name testHuaweiConnection
* @description test connection to Huawei Cloud API using PHP cURL
* @private
*/
private function testHuaweiConnection($account)
{
try
{
# Check if we have project_id and domain_id (full IAM auth) or just keys (simple auth)
$hasProjectInfo = !empty($account['project_id']) && $account['project_id'] !== '0';
$hasDomainInfo = !empty($account['domain_id']) && $account['domain_id'] !== '0';
if ($hasProjectInfo && $hasDomainInfo) {
# Full IAM authentication with project and domain
return $this->testHuaweiConnectionIAM($account);
} else {
# Simple authentication with just Application Key and Secret Key
return $this->testHuaweiConnectionSimple($account);
}
}
catch(Exception $e)
{
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* @name testHuaweiConnectionIAM
* @description test connection using full IAM authentication
* @private
*/
private function testHuaweiConnectionIAM($account)
{
try
{
# Huawei Cloud IAM endpoint for getting token
$iamEndpoint = 'https://iam.' . $account['region'] . '.myhuaweicloud.com/v3/auth/tokens';
# Prepare authentication payload
$authPayload = [
'auth' => [
'identity' => [
'methods' => ['password'],
'password' => [
'user' => [
'access_key' => $account['access_key'],
'secret_key' => $account['secret_key'],
'domain' => [
'id' => $account['domain_id']
]
]
]
],
'scope' => [
'project' => [
'id' => $account['project_id']
]
]
]
];
# Initialize cURL
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $iamEndpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: Wevads-HuaweiTest/1.0'
],
CURLOPT_POSTFIELDS => json_encode($authPayload),
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_HEADER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if($curlError)
{
return ['success' => false, 'error' => 'cURL Error: ' . $curlError];
}
if($httpCode >= 200 && $httpCode < 300)
{
return ['success' => true, 'message' => 'Authentication successful'];
}
else
{
return ['success' => false, 'error' => 'HTTP ' . $httpCode . ' - Invalid credentials or configuration'];
}
}
catch(Exception $e)
{
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* @name testHuaweiConnectionSimple
* @description test connection using Application Key and Secret Key only
* @private
*/
private function testHuaweiConnectionSimple($account)
{
try
{
# For simple auth, we'll test IAM endpoint which is more basic
# and should work with basic Application Key permissions
$region = $account['region'] ?: 'eu-west-101';
$iamEndpoint = 'https://iam.' . $region . '.myhuaweicloud.com/v3/regions';
# Generate authentication headers using Application Key and Secret Key
$timestamp = gmdate('Ymd\THis\Z');
$date = gmdate('Ymd');
# Create canonical request for IAM regions endpoint (more permissive)
$method = 'GET';
$canonicalUri = '/v3/regions';
$canonicalQueryString = ''; # No query parameters needed
$canonicalHeaders = "host:iam.{$region}.myhuaweicloud.com\nx-sdk-date:{$timestamp}\n";
$signedHeaders = 'host;x-sdk-date';
$payloadHash = hash('sha256', '');
$canonicalRequest = "{$method}\n{$canonicalUri}\n{$canonicalQueryString}\n{$canonicalHeaders}\n{$signedHeaders}\n{$payloadHash}";
# Create string to sign
$algorithm = 'SDK-HMAC-SHA256';
$credentialScope = "{$date}/{$region}/iam/sdk_request";
$stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n" . hash('sha256', $canonicalRequest);
# Calculate signature
$signingKey = hash_hmac('sha256', $credentialScope, 'SDK' . $account['secret_key'], true);
$signature = hash_hmac('sha256', $stringToSign, $signingKey);
# Build authorization header
$authorization = "{$algorithm} Credential={$account['access_key']}/{$credentialScope}, SignedHeaders={$signedHeaders}, Signature={$signature}";
# Make the test request to IAM regions (basic endpoint)
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $iamEndpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Host: iam.' . $region . '.myhuaweicloud.com',
'X-Sdk-Date: ' . $timestamp,
'Authorization: ' . $authorization,
'Content-Type: application/json'
],
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if($curlError)
{
return ['success' => false, 'error' => 'cURL Error: ' . $curlError];
}
if($httpCode >= 200 && $httpCode < 300)
{
return ['success' => true, 'message' => 'Application Key authentication successful - IAM API accessible'];
}
else if($httpCode == 401)
{
return ['success' => false, 'error' => 'HTTP 401 - Invalid Application Key or Secret Key'];
}
else if($httpCode == 403)
{
# If IAM gives 403, try a simpler test - just validate key format and signature
return $this->testHuaweiKeysBasic($account);
}
else
{
return ['success' => false, 'error' => 'HTTP ' . $httpCode . ' - Connection test failed'];
}
}
catch(Exception $e)
{
return ['success' => false, 'error' => $e->getMessage()];
}
}
/**
* @name testHuaweiKeysBasic
* @description basic validation of Huawei keys format and signature
* @private
*/
private function testHuaweiKeysBasic($account)
{
try
{
# Validate key formats
$accessKey = $account['access_key'];
$secretKey = $account['secret_key'];
if(empty($accessKey) || empty($secretKey))
{
return ['success' => false, 'error' => 'Application Key or Secret Key is empty'];
}
# Check if keys have reasonable format (Huawei keys are typically 20-40 chars)
if(strlen($accessKey) < 10 || strlen($accessKey) > 50)
{
return ['success' => false, 'error' => 'Application Key format appears invalid (wrong length)'];
}
if(strlen($secretKey) < 20 || strlen($secretKey) > 100)
{
return ['success' => false, 'error' => 'Secret Key format appears invalid (wrong length)'];
}
# Test signature generation (if this works, keys are likely valid)
$timestamp = gmdate('Ymd\THis\Z');
$date = gmdate('Ymd');
$region = $account['region'] ?: 'eu-west-101';
$testString = "SDK-HMAC-SHA256\n{$timestamp}\n{$date}/{$region}/test/sdk_request\ntest";
$signingKey = hash_hmac('sha256', "{$date}/{$region}/test/sdk_request", 'SDK' . $secretKey, true);
$signature = hash_hmac('sha256', $testString, $signingKey);
if(empty($signature) || strlen($signature) != 64)
{
return ['success' => false, 'error' => 'Failed to generate valid signature - Secret Key may be invalid'];
}
return ['success' => true, 'message' => 'Keys format validated - Authentication likely works but API access may be restricted'];
}
catch(Exception $e)
{
return ['success' => false, 'error' => 'Key validation error: ' . $e->getMessage()];
}
}
/**
* @name save
* @description the save action
* @before init
* @after closeConnections
*/
public function save()
{
$logFile = '/opt/wevads/storage/logs/huawei_error.log';
$message = 'Internal server error !';
$flag = 'error';
try
{
# get post data
$data = $this->app->http->request->retrieve(Request::ALL,Request::POST);
if(count($data))
{
$update = false;
$account = new HuaweiAccount();
# ensure authenticated user exists
if(!isset($this->authenticatedUser) || $this->authenticatedUser == null)
{
throw new PageException('Access Denied !',403);
}
$username = $this->authenticatedUser->getEmail();
# update case
if($this->app->utils->arrays->get($data,'id') > 0)
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,'edit');
if($access == false)
{
throw new PageException('Access Denied !',403);
}
$update = true;
$message = 'Record updated succesfully !';
$account->setId(intval($this->app->utils->arrays->get($data,'id')));
$account->load();
$account->setLastUpdatedBy($username);
$account->setLastUpdatedDate(date('Y-m-d'));
}
else
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,'add');
if($access == false)
{
throw new PageException('Access Denied !',403);
}
$message = 'Record stored succesfully !';
$account->setCreatedBy($username);
$account->setCreatedDate(date('Y-m-d'));
$account->setLastUpdatedBy($username);
$account->setLastUpdatedDate(date('Y-m-d'));
}
$account->setName($this->app->utils->arrays->get($data,'huawei-name'));
$account->setStatus($this->app->utils->arrays->get($data,'huawei-status'));
$account->setAccessKey($this->app->utils->arrays->get($data,'huawei-access-key'));
$account->setSecretKey($this->app->utils->arrays->get($data,'huawei-secret-key'));
$account->setProjectId($this->app->utils->arrays->get($data,'huawei-project-id'));
$account->setDomainId($this->app->utils->arrays->get($data,'huawei-domain-id'));
$account->setRegion($this->app->utils->arrays->get($data,'huawei-region'));
$result = $update == false ? $account->insert() : $account->update();
if($result > -1)
{
$flag = 'success';
}
# safe debug log (no secrets)
@file_put_contents($logFile,
'[' . date('Y-m-d H:i:s') . "] HuaweiAccounts::save ok flag=".$flag.
" update=".($update?'1':'0').
" id=".$this->app->utils->arrays->get($data,'id')."\n",
FILE_APPEND
);
}
}
catch(\Throwable $e)
{
# log detailed error for debugging
@file_put_contents($logFile,
'[' . date('Y-m-d H:i:s') . "] HuaweiAccounts::save error: ". $e->getMessage() .
" in ". $e->getFile() .":". $e->getLine() . "\n",
FILE_APPEND
);
}
# stores the message in the session
Page::registerMessage($flag, $message);
# redirect to lists page
Page::redirect();
}
/**
* @name getRegions
* @description get available regions from Huawei Cloud API
* @before init
* @after closeConnections
*/
public function getRegions()
{
# check for permissions
$access = Permissions::checkForAuthorization($this->authenticatedUser,__CLASS__,'main');
if($access == false)
{
Page::printApiResults(403,'Access Denied !');
}
# get post data
$data = $this->app->http->request->retrieve(Request::ALL,Request::POST);
if(!isset($data['account-id']) || !is_numeric($data['account-id']))
{
Page::printApiResults(400,'Invalid account ID !');
}
$accountId = intval($data['account-id']);
$account = HuaweiAccount::first(HuaweiAccount::FETCH_ARRAY,['id = ?',$accountId]);
if(count($account) == 0)
{
Page::printApiResults(404,'Account not found !');
}
try
{
# Get regions from Huawei Cloud API
$result = $this->fetchHuaweiRegions($account);
if($result['success'])
{
Page::printApiResults(200,'Regions retrieved successfully !', ['regions' => $result['regions']]);
}
else
{
Page::printApiResults(400,'Failed to get regions: ' . $result['error'], ['regions' => []]);
}
}
catch(Exception $e)
{
Page::printApiResults(500,'Error getting regions: ' . $e->getMessage(), ['regions' => []]);
}
}
/**
* @name fetchHuaweiRegions
* @description fetch available regions from Huawei Cloud API
* @private
*/
private function fetchHuaweiRegions($account)
{
try
{
# Use a default region to get the list of all regions
$defaultRegion = $account['region'] ?: 'eu-west-101';
$iamEndpoint = 'https://iam.' . $defaultRegion . '.myhuaweicloud.com/v3/regions';
# Generate authentication headers
$timestamp = gmdate('Ymd\THis\Z');
$date = gmdate('Ymd');
# Create canonical request
$method = 'GET';
$canonicalUri = '/v3/regions';
$canonicalQueryString = '';
$canonicalHeaders = "host:iam.{$defaultRegion}.myhuaweicloud.com\nx-sdk-date:{$timestamp}\n";
$signedHeaders = 'host;x-sdk-date';
$payloadHash = hash('sha256', '');
$canonicalRequest = "{$method}\n{$canonicalUri}\n{$canonicalQueryString}\n{$canonicalHeaders}\n{$signedHeaders}\n{$payloadHash}";
# Create string to sign
$algorithm = 'SDK-HMAC-SHA256';
$credentialScope = "{$date}/{$defaultRegion}/iam/sdk_request";
$stringToSign = "{$algorithm}\n{$timestamp}\n{$credentialScope}\n" . hash('sha256', $canonicalRequest);
# Calculate signature
$signingKey = hash_hmac('sha256', $credentialScope, 'SDK' . $account['secret_key'], true);
$signature = hash_hmac('sha256', $stringToSign, $signingKey);
# Build authorization header
$authorization = "{$algorithm} Credential={$account['access_key']}/{$credentialScope}, SignedHeaders={$signedHeaders}, Signature={$signature}";
# Make the API request
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $iamEndpoint,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Host: iam.' . $defaultRegion . '.myhuaweicloud.com',
'X-Sdk-Date: ' . $timestamp,
'Authorization: ' . $authorization,
'Content-Type: application/json'
],
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if($curlError)
{
return ['success' => false, 'error' => 'cURL Error: ' . $curlError];
}
if($httpCode >= 200 && $httpCode < 300)
{
$responseData = json_decode($response, true);
if(isset($responseData['regions']) && is_array($responseData['regions']))
{
$regions = [];
foreach($responseData['regions'] as $region)
{
if(isset($region['id']) && isset($region['description']))
{
$regions[] = [
'id' => $region['id'],
'name' => $region['description']
];
}
}
return ['success' => true, 'regions' => $regions];
}
else
{
# Fallback to common Huawei regions if API response is unexpected
return $this->getDefaultHuaweiRegions();
}
}
else
{
# If API fails, return common regions as fallback
return $this->getDefaultHuaweiRegions();
}
}
catch(Exception $e)
{
# Return default regions as fallback
return $this->getDefaultHuaweiRegions();
}
}
/**
* @name getDefaultHuaweiRegions
* @description get default Huawei Cloud regions as fallback
* @private
*/
private function getDefaultHuaweiRegions()
{
$defaultRegions = [
['id' => 'cn-north-1', 'name' => 'Beijing (China North-Beijing1)'],
['id' => 'cn-north-4', 'name' => 'Beijing (China North-Beijing4)'],
['id' => 'cn-east-2', 'name' => 'Shanghai (China East-Shanghai2)'],
['id' => 'cn-east-3', 'name' => 'Shanghai (China East-Shanghai1)'],
['id' => 'cn-south-1', 'name' => 'Guangzhou (China South-Guangzhou)'],
['id' => 'ap-southeast-1', 'name' => 'Hong Kong (Asia Pacific-Hong Kong)'],
['id' => 'ap-southeast-2', 'name' => 'Bangkok (Asia Pacific-Bangkok)'],
['id' => 'ap-southeast-3', 'name' => 'Singapore (Asia Pacific-Singapore)'],
['id' => 'me-east-1', 'name' => 'Riyadh (Middle East-Riyadh)'],
['id' => 'af-south-1', 'name' => 'Johannesburg (Africa-Johannesburg)'],
['id' => 'eu-west-101', 'name' => 'Dublin (Europe-Dublin)'],
['id' => 'eu-west-0', 'name' => 'Paris (Europe-Paris)'],
['id' => 'us-east-1', 'name' => 'Virginia (US East-Virginia)'],
['id' => 'us-west-1', 'name' => 'Silicon Valley (US West-Silicon Valley)'],
['id' => 'sa-brazil-1', 'name' => 'São Paulo (Latin America-São Paulo1)'],
['id' => 'la-south-2', 'name' => 'Santiago (Latin America-Santiago)']
];
return ['success' => true, 'regions' => $defaultRegions];
}
/**
* @name closeConnections
* @description close all connections
* @once
* @protected
*/
public function closeConnections()
{
# connect to the database
$this->app->database('system')->disconnect();
$this->app->database('clients')->disconnect();
}
/**
* @name checkForMessage
* @description checks for session messages
* @once
* @protected
*/
public function checkForMessage()
{
# check for message
Page::checkForMessage($this);
}
}