S3 files, unAuth methods in API, bugifx

s3 files download, packages version fix, oauth fix, time intervals,
This commit is contained in:
Pavel Dmitriev 2022-08-13 12:31:47 +03:00
parent 872da83978
commit f59fdf61ca
25 changed files with 658 additions and 41 deletions

111
.drone.yml Normal file
View File

@ -0,0 +1,111 @@
---
kind: pipeline
name: SonarQube check
image_pull_secrets:
- dockerconfig
trigger:
branch:
- develop
event:
- push
- custom
steps:
- name: SonarQube check
image: sonarsource/sonar-scanner-cli
environment:
SONAR_PROJECT:
from_secret: sonarProjectId
SONAR_TOKEN:
from_secret: sonarToken
SONAR_HOST:
from_secret: sonarHost
TEST: test
commands:
- sonar-scanner -Dsonar.projectKey=$${SONAR_PROJECT} -Dsonar.sources=. -Dsonar.host.url=$${SONAR_HOST} -Dsonar.login=$${SONAR_TOKEN}
depends_on:
- clone
---
kind: pipeline
name: Build image
image_pull_secrets:
- dockerconfig
trigger:
ref:
- refs/heads/testing
- refs/heads/master
- refs/tags/*
steps:
- name: Prepare image
image: mxfox.ru/mxfox/fox-web-basic:latest
commands:
- |
apt-get update -y
apt-get install curl -y
cd /tmp
curl -sS https://getcomposer.org/installer -o composer-setup.php
php composer-setup.php --install-dir=/usr/local/bin --filename=composer
DEBIAN_FRONTEND=noninteractive TZ=Europe/Moscow apt-get -y install tzdata
cd -
- composer install
- |
find . -name "*~" -prune -exec rm -rf '{}' \;
find . -name "*.bak" -prune -exec rm -rf '{}' \;
find . -name "*.old" -prune -exec rm -rf '{}' \;
find . -name ".git" -prune -exec rm -rf '{}' \;
find . -name ".settings" -prune -exec rm -rf '{}' \;
find . -name ".buildpath" -prune -exec rm -rf '{}' \;
find . -name ".project" -prune -exec rm -rf '{}' \;
find . -name "README.*" -prune -exec rm -rf '{}' \;
find . -name "*.md" -prune -exec rm -rf '{}' \;
find . -name "composer.*" -prune -exec rm -rf '{}' \;
find . -name ".travis*" -prune -exec rm -rf '{}' \;
find . -name "installed.json" -prune -exec rm -rf '{}' \;
find . -name "*.sample" -prune -exec rm -rf '{}' \;
find . -type d ! -path './.git/**' ! -path './static/**' ! -path "./static" ! -path ./*/modules/*/static* -exec bash -c 'test -f {}/.htaccess && echo -n "[ SKIP ] " || (cp ./docker-build/.htaccess {} && echo -n "[ ADD ] ") && echo {}/.htaccess' \;
cp docker-build/Dockerfile Dockerfile
rm -f composer.*
- name: Build docker image
image: mxfox.ru/mxfox/docker-dind.buildx:latest
privileged: true
environment:
DOCKER_AUTH:
from_secret: dockerconfig
IMAGE_PREFIX: mxfox.ru/mxfox/chimera-mk2-core
commands:
- buildx-bgstart.sh
- echo $${DOCKER_AUTH} > ~/.docker/config.json
- echo "CB ${CI_COMMIT_BRANCH}"
- echo "DT ${DRONE_TAG}"
- |
if [ -n "${DRONE_TAG}" ]
then
export xBuildSuffix=" -t $${IMAGE_PREFIX}:${DRONE_TAG} -t $${IMAGE_PREFIX}:latest --push"
else
export xBuildSuffix=" -t $${IMAGE_PREFIX}:${CI_COMMIT_BRANCH}-${CI_BUILD_NUMBER}-${DRONE_COMMIT_SHA:0:10} -t $${IMAGE_PREFIX}:${CI_COMMIT_BRANCH} --push"
fi
- docker buildx build --platform linux/amd64,linux/arm64 . $${xBuildSuffix}
---
kind: pipeline
name: Export code
image_pull_secrets:
- dockerconfig
trigger:
ref:
- refs/tags/*
steps:
- name: Export code
image: mxfox.ru/mxfox/fox-web-basic:latest
commands:
- echo "Not implemented yet"

3
.gitignore vendored
View File

@ -5,4 +5,5 @@
/temp-test
/composer.lock
/vendor
.scannerwork
.scannerwork
.vscode

View File

@ -1,3 +1,63 @@
## V.1.0.0
1. Add disabled flag for createLeftPanel and crealeTabsPanel
2. Remove context menu title if empty title
3. onEnter for fieldGroup
4. Поиск по группам пользователей
5. Поиск по пользователям
6. onContextMenu для поля UI.fieldAdd(type=label)
7. Кликабельность поля типа "label" для UI.fieldAdd
8. Исправлена ошибка загрузки модуля в Firefox
9. Исправлена ошибка /users/x
10. Исправлена Ошибка создания форм с полем passwordNew
11. Исправлено Изменение хэша при изменение URL oAuth
12. Установка модулей и fox-start.d
13. Скрывать меню при обновлении
14. Добавить подпись Powered by chimera fox как было на mark1
15. Добавить заглушку на случай если браузер не поддерживается
17. Установщик пакетов с модулями
18. Экранирование параметра id в baseClass::__construct() @type=string|int
19. added searchResult class, updated system to use it in search
20. Implemented fox\modules::getByFeature and fox\moduleInfo::getByFeature
21. added UI.collectForm().getVals() method
22. Updated session/modules logic
23. Added chars and length props for passwordNew field, added string ref type for smartClick
24. Added Filrebase/jwt into composer.json, added id for tabs-anchor
25. Added API.session.checkAccess(rule, module)
26. added qGetSql() and qGetSqlSelectTemplate()
27. Updated UI panels
28. Implemented usage of user session config if exists
29. Added public function isMember(user $user)
30. Added href (smartClick) type into UI.addField()
31. Added click-protection for foxClick: click blocked if selection not empty
32. table.datatable td(th).icon - icon size reduced
33. Updated Breadcrumbs for use objects
34. Added attrs.disabled translation into item for UI.fieldAdd
35. Implemented companies management
36. Added onClick and onContextMenu for fieldBlock, added .onEnter ext.
37. Added async key into api.exec and disabled flag into LR-tabs
38. API.session.checkAccess fix
39. Added tabs manipulation methods
40. Switched common::getGUIDc to UUID:v4
41. Added UI.getClipboard(callback) function
42. Added onFail callback into UI.getClipboard
43. Iplemented ref.onContextMenu for adField with type label and href
44. Added API.loadModule method
45. Blanker z-index changed
46. added baseClass->search page and pagesize type conversion
47. Added marker into s3client->listobjects method, added listAllObjects and added forced garbage collection
48. Added $__foxRequestInstance
49. Added count into default search, fixed foxpager
50. added UI.stamp2isodateq and UI.stamp2isodatens funcitons
51. Added attrs into accordion panel
52. Updated xSearch to extend orderBy
53. Webhook implemented
54. fox\request @property-read $rawRequestBody
55. multipart cache
(Last commit processed #6b3a7f420d)
## V.0.9.0 RC
### Бэк
1. Огранизация прозрачного REST API через интерфейс fox\externalCallable - позволяет с минимальными затратами но при этом со всеми необходимыми проверками организовать доступ к нужным сущностям.

View File

@ -30,7 +30,8 @@ try {
}
$request->shift();
$modules=moduleInfo::getAll();
if (!array_key_exists(request::get()->module, $modules)) {
$reqModule=request::get()->module;
if (!array_key_exists($reqModule, $modules)) {
if (request::get()->authOK) {
throw new foxException("Invalid module",404);
} else {
@ -38,11 +39,8 @@ try {
}
}
if ($modules[request::get()->module]->authRequired && !request::get()->authOK) {
throw new foxException("Unauthorized",401);
}
$modNS=$modules[request::get()->module]->namespace;
$modNS=$modules[$reqModule]->namespace;
request::get()->shift();
$className=$modNS."\\".request::get()->module;
if (!class_exists($className)) {
@ -60,24 +58,46 @@ try {
$apiFunction=fox\common::clearInput($request->function,"a-zA-Z0-9");
$apiXFunction=empty($request->parameters[0])?NULL:fox\common::clearInput($request->parameters[0],"a-zA-Z0-9");
$apiCallMethod="API_".$apiMethod."_".$apiFunction;
$apiXCallMethod="APIX_".$apiMethod."_".$apiXFunction;
$apiZCallMethod="API_".$apiMethod;
if (method_exists($className, $apiCallMethod)) {
$rv=$className::$apiCallMethod($request);
} else if (($apiXFunction!==null) && method_exists($className, $apiXCallMethod)) {
$rv=$className::$apiXCallMethod($request);
} else if (method_exists($className, $apiZCallMethod)) {
$rv=$className::$apiZCallMethod($request);
} else if (method_exists($className, "APICall")) {
$rv=$className::apiCall(request::get());
if ($modules[$reqModule]->authRequired && !request::get()->authOK) {
$apiPCallMethod="API_UnAuth_".$apiMethod."_".$apiFunction;
$apiPXCallMethod="APIX_UnAuth_".$apiMethod."_".$apiXFunction;
$apiPZCallMethod="API_UnAuth_".$apiMethod;
if (method_exists($className, $apiPCallMethod)) {
$rv=$className::$apiPCallMethod($request);
} else if (($apiXFunction!==null) && method_exists($className, $apiPXCallMethod)) {
$rv=$className::$apiPXCallMethod($request);
} else if (method_exists($className, $apiPZCallMethod)) {
$rv=$className::$apiPZCallMethod($request);
} else {
throw new foxException("Unauthorized",401);
}
foxRequestResult::throw("200", "OK", $rv);
} else {
throw new foxException("Method not allowed", 405);
$apiCallMethod="API_".$apiMethod."_".$apiFunction;
$apiXCallMethod="APIX_".$apiMethod."_".$apiXFunction;
$apiZCallMethod="API_".$apiMethod;
if (method_exists($className, $apiCallMethod)) {
$rv=$className::$apiCallMethod($request);
} else if (($apiXFunction!==null) && method_exists($className, $apiXCallMethod)) {
$rv=$className::$apiXCallMethod($request);
} else if (method_exists($className, $apiZCallMethod)) {
$rv=$className::$apiZCallMethod($request);
} else if (method_exists($className, "APICall")) {
$rv=$className::apiCall(request::get());
} else {
throw new foxException("Method not allowed", 405);
}
foxRequestResult::throw("200", "OK", $rv);
}
foxRequestResult::throw("200", "OK", $rv);
} catch (fox\foxRequestResult $e) {
ob_clean();
header('Content-Type: application/json; charset=utf-8');

View File

@ -2,6 +2,7 @@
use fox\modules;
use fox\config;
use fox\moduleInfo;
include(__DIR__."/../Autoloader.php");
@ -49,6 +50,18 @@ foreach (scandir(modules::packagesDir) as $file) {
if (file_exists(modules::modulesDir."/".$newModInfo->name."/fox-start.d")) {
system("chmod a+x \"".modules::modulesDir."/".$newModInfo->name."/fox-start.d/\"*");
}
$mods = moduleInfo::getAll();
foreach($mods as $mod) {
if ($newModInfo->name==$mod->instanceOf) {
$mod->modVersion=$newModInfo->version;
$mod->modBuild=$newModInfo->build;
$mod->save();
}
}
print "OK\n";
}
}

View File

@ -14,6 +14,8 @@ namespace fox;
class cache
{
protected const chunkSize=1024000;
public ?\Memcached $mcd = null;
protected $prefix = null;
@ -84,9 +86,26 @@ class cache
return false;
}
if ($encrypt) {
$this->mcd->set($this->prefix . "." . $key, xcrypt::encrypt(json_encode($val)), $TTL);
$str=xcrypt::encrypt(json_encode($val));
} else {
$this->mcd->set($this->prefix . "." . $key, json_encode($val), $TTL);
$str=json_encode($val);
}
$this->del($key);
$len=strlen($str);
if ($len <= static::chunkSize) {
$this->mcd->set($this->prefix . "." . $key, $str, $TTL);
} else {
# multipart
$md5=md5($str);
$chunks=ceil($len/static::chunkSize);
$this->mcd->set($this->prefix . "." . $key.".MPX00", json_encode(["len"=>$len, "md5"=>$md5,"chunks"=>$chunks]), $TTL);
for ($i = 0; $i<$chunks; $i++) {
$xzval=substr($str,$i*static::chunkSize,static::chunkSize);
$this->mcd->set($this->prefix . "." . $key.".MPX0".($i+1), $xzval, $TTL);
}
}
}
@ -101,10 +120,31 @@ class cache
$xval=$this->mcd->get($this->prefix . "." . $key);
if ($xval==null) {
$idx=$this->mcd->get($this->prefix . "." . $key.".MPX00");
if ($idx) {
$idx=json_decode($idx);
if ($idx) {
$xval="";
for ($i=1; $i <=$idx->chunks; $i++) {
$xzval=$this->mcd->get($this->prefix . "." . $key.".MPX0".$i);
$xval .= $xzval;
}
$xlen=strlen($xval);
$xmd5=md5($xval);
if ($xlen!=$idx->len || $xmd5!=$idx->md5) {
$this->del($key);
$xval=null;
}
}
}
}
if ($xval==null || $xval=="null") { return null; }
$rv= json_decode($this->mcd->get($this->prefix . "." . $key), $array);
$rv= json_decode($xval, $array);
if ($rv !== null) { return $rv; }
return json_decode(xcrypt::decrypt($this->mcd->get($this->prefix . "." . $key)), $array);
return json_decode(xcrypt::decrypt($xval), $array);
}
public function del($key) {
@ -116,6 +156,17 @@ class cache
}
$this->mcd->delete($this->prefix . "." . $key);
$idx=$this->mcd->get($this->prefix . "." . $key.".MPX00");
if ($idx) {
$idx=json_decode($idx);
if ($idx) {
$this->mcd->delete($this->prefix . "." . $key.".MPX00");
for ($i=1; $i <=$idx->chunks; $i++) {
$this->mcd->delete($this->prefix . "." . $key.".MPX0".$i);
}
}
}
}
}

View File

@ -4,7 +4,8 @@ namespace fox;
/**
*
* Class fox\common
*
* Common methods ex. generation GUID
*
* @copyright MX STAR LLC 2021
* @version 4.0.0
* @author Pavel Dmitriev
@ -88,14 +89,34 @@ class common
return $str;
}
/**
* Generate uuid withous curly braces in lowercase
* xxx....xxx
**/
static function getUUID()
{
return strtolower(UUID::v4());
}
/**
* Generate GUID withous curly braces
* XXX.....XXX
**/
static function getGUIDc()
{
return strtoupper(UUID::v4());
}
/**
* Generate GUID with curly braces
* { XXX.....XXX }
**/
static function getGUID()
{
return chr(123) . getGUIDc() . chr(125);
return chr(123) . static::getGUIDc() . chr(125);
}
static function fullname2qname($first, $mid, $last)

View File

@ -1,5 +1,8 @@
<?php namespace fox;
use fox\meta\settings;
use stdClass;
/**
*
* Class fox\file
@ -11,23 +14,37 @@
*
**/
class file extends baseClass {
class file extends baseClass implements externalCallable {
protected $id;
public $uuid;
public $fileName;
public $module;
public $class;
public $refId;
public $public=false;
public $private=false;
public $ownerId=null;
public time $uploadStamp;
public time $expireStamp;
protected $__data;
public static $sqlTable="tblFiles";
# file access token expires in 30 seconds
const fileTokenTTL=30;
const defaultBucket="files";
protected function __xConstruct() {
$this->expireStamp=new time();
$this->uploadStamp=time::current();
}
public static $sqlColumns = [
"uuid" => [
"type" => "CHAR(36)",
"index"=>"UNIQUE",
],
"fileName" => [
"type" => "VARCHAR(255)",
],
@ -39,6 +56,10 @@ class file extends baseClass {
"type" => "VARCHAR(255)",
"index" => "INDEX"
],
"refId" => [
"type" => "VARCHAR(255)",
],
"ownerId" => [
"type" => "INT",
"index" => "INDEX",
@ -49,9 +70,134 @@ class file extends baseClass {
"index" => "INDEX",
"nullable"=>true
],
"uploadStamp" => [
"type" => "DATETIME",
"index" => "INDEX",
"nullable"=>true,
],
];
protected function validateSave()
{
if (empty($this->uuid)) { $this->uuid = common::getUUID(); }
return true;
}
protected static function xSearch($where, $pattern, ?array $options, sql $sql) {
$xWhere="";
if (!empty($options["uuid"])) {
$xWhere .= ($xWhere?" AND ":"")." `uuid` = '".common::clearInput($options["uuid"],"0-9a-z-")."'";
}
if (!empty($options["instance"])) {
$xWhere .= ($xWhere?" AND ":"")." `module` = '".common::clearInput($options["instance"])."'";
}
if (!empty($options["class"])) {
$xWhere .= ($xWhere?" AND ":"")." `class` = '".common::clearInput($options["class"],'0-9A-Za-z_\.\-\\\/')."'";
}
if (!empty($options["refId"])) {
$xWhere .= ($xWhere?" AND ":"")." `refId` = '".common::clearInput($options["refId"])."'";
}
if ($xWhere) {
if ($where) {
$where = "(".$where.") AND (".$xWhere.")";
} else {
$where = $xWhere;
}
}
return ["where"=>$where, "join"=>null];
}
protected static function prepareFileUpload($data, $fileName, object $ref, string $instance=null, user $owner=null, $ttl=null) {
$f=new static();
$f->fileName=$fileName;
$f->module=$instance;
if ($owner) { $f->ownerId=$owner->id; }
if ($ttl) { $f->expireStamp=time::current()->addSec($ttl); }
$f->class = $ref::class;
$f->uuid=common::getUUID();
return $f;
}
public static function directUpload($data, $fileName, object $ref, string $instance=null, user $owner=null, $ttl=null) {
$f=static::prepareFileUpload($data, $fileName, $ref, $instance, $owner, $ttl);
$s3=new s3client();
if (!$s3->headBucket(static::defaultBucket)) {
$s3->createBucket(static::defaultBucket);
}
$s3->putObject(static::defaultBucket,$f->uuid, $data);
$f->save();
return $f;
}
public static function getByUUID(string $uuid) : file {
$files = file::search(options: ["uuid"=>$uuid]);
if ($files->result) {
return array_shift($files->result);
} else {
throw new foxException("File not found", 404);
}
}
public function getContent() {
if ($this->uuid) {
$s3=new s3client();
return $s3->getObject(static::defaultBucket, $this->uuid);
}
}
public function getDownloadToken() {
if (empty($this->id)) {
throw new foxException("Empty fileId not allowed here");
};
$token=common::genPasswd(32);
$c=new cache();
$c->set('fileDnldToken-'.$token,$this,static::fileTokenTTL);
return $token;
}
public static function getByToken($token) {
$c=new cache();
$ftile=$c->get('fileDnldToken-'.$token,true);
if ($ftile) {
$file = new static($ftile);
$c->del('fileDnldToken-'.$token);
return $file;
} else {
throw new foxException("Invalid token",404);
}
}
public static function getUploadToken($data, $fileName, object $ref, string $instance=null, user $owner=null) {
$f=static::prepareFileUpload($data, $fileName, $ref, $instance, $owner);
$token=common::genPasswd(32);
$c=new cache();
$c->set('fileUpldToken-'.$token,$f,static::fileTokenTTL);
return $token;
}
#### REST API
public static function API_UnAuth_GET(request $request) {
if (!empty($request->parameters)) throw new foxException("Invalid request", 400);
$file2=file::getByToken($request->function);
header_remove('Content-Type');
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$file2->fileName);
print($file2->getContent());
exit;
}
}
?>

View File

@ -33,6 +33,8 @@ class moduleInfo extends baseClass implements externalCallable
public string $modVersion = "1.0.0";
public ?string $modBuild=null;
public string $modPriority = "9999";
public time $installDate;
@ -67,6 +69,9 @@ class moduleInfo extends baseClass implements externalCallable
"type" => "INT",
"index" => "INDEX"
],
"modBuild"=> [
"type"=>"VARCHAR(255)",
],
"singleInstanceOnly" => [
"type" => "SKIP"
],

View File

@ -162,6 +162,7 @@ class modules implements externalCallable
if ($modDesc->name != $modInfo->name) { throw new foxException("Module name mismatch for ".$modInfo->name); }
$modInfo->title=$modDesc->title;
$modInfo->modVersion=$modDesc->version;
@$modInfo->modBuild=$modDesc->build;
} catch (\Exception $e) {
trigger_error($e->getMessage());
continue;

View File

@ -40,6 +40,7 @@ class oAuthProfile extends baseClass implements externalCallable {
protected function validateSave()
{
if (empty($this->hash)) {
trigger_error($this->hash);
$this->hash=xcrypt::hash(json_encode($this));
}
return true;
@ -142,7 +143,7 @@ class oAuthProfile extends baseClass implements externalCallable {
if (!empty($request->requestBody->clientId)) { $m->clientId=common::clearInput($request->requestBody->clientId,"0-9A-Za-z._:/-"); }
if (!empty($request->requestBody->clientKey)) { $m->clientKey=common::clearInput($request->requestBody->clientKey,"0-9A-Za-z._@-"); }
$m->config=$request->requestBody->config;
$m->hash=common::clearInput($request->requestBody->hash,"0-9A-Za-z._@-");
if (property_exists($request->requestBody,"hash")) { $m->hash=common::clearInput($request->requestBody->hash,"0-9A-Za-z._@-"); }
$m->save();
static::log($request->instance,__FUNCTION__, "OAuth profile ".$m->name." updated.",$request->user,"oAuthProfile",$m->id,null,logEntry::sevInfo,["changelog"=>$m->changelog]);
}

View File

@ -28,5 +28,9 @@ interface objectStorageClient
public function createBucket($bucket);
public function deleteBucket($bucket);
public function headBucket($bucket);
public function headObject($bucket, $key);
}
?>

View File

@ -12,6 +12,7 @@ namespace fox;
*
* @property-read user $user
* @property-read $userId
* @property-read $rawRequestBody
*
*/
class request extends baseClass implements noSqlMigration
@ -164,6 +165,8 @@ class request extends baseClass implements noSqlMigration
throw new foxException('Unauthorized', 401);
}
return $this->token->user;
case "rawRequestBody":
return file_get_contents("php://input");
default:
return parent::__get($key);
}

View File

@ -1,6 +1,8 @@
<?php
namespace fox;
use Exception;
/**
*
* Class fox\s3client
@ -139,6 +141,22 @@ class s3client implements objectStorageClient
public function __destruct() {
$this->s3=null;
}
public function headBucket($bucket) {
try {
if ($this->s3->headBucket(["Bucket" => $this->prefix . $bucket])) {
return true;
}
} catch (Exception $e) {
return false;
}
}
public function headObject($bucket, $key) {
return false;
}
}
?>

View File

@ -100,6 +100,18 @@ class time implements stringExportable, stringImportable, \JsonSerializable
return $this->stamp;
}
public static function formatInterval($val) {
$durWeeks=floor($val/604800);
$durXWeeks=$val % 604800;
$durDays=floor($durXWeeks/86400);
$durXDays=$durXWeeks % 86400;
$durHrs=floor($durXDays/3600);
$durXHrs=$durXDays % 3600;
$durMins=floor($durXHrs/60);
$durSecs=floor($durXHrs % 60);
return ($durWeeks>0?$durWeeks."W ":"").($durDays>0?$durDays."D ":"").str_pad($durHrs,2, "0", STR_PAD_LEFT).":".str_pad($durMins,2, "0", STR_PAD_LEFT).":".str_pad($durSecs,2, "0", STR_PAD_LEFT);
}
}
?>

View File

@ -0,0 +1,25 @@
<?php
namespace fox;
/**
*
* Interface fox\externalCallable
*
* @copyright MX STAR LLC 2021
* @version 4.0.0
* @author Pavel Dmitriev
* @license GPLv3
*
*/
interface webhookCallable
{
/*
* public static function HOOK_<method>_<function>(request $request);
* Example:
* public static function HOOK_POST_members(request $request);
* public static function HOOKCall(request $request);
*
*/
}
?>

View File

@ -31,8 +31,6 @@ class xcrypt
static function decrypt($val, $key = null)
{
$ENC_KEY = substr(md5(config::get("masterSecret")), 0, 24);
if (isset($key)) {
$ENC_KEY = substr(md5($key), 0, 24);
} else {

4
hook/.htaccess Normal file
View File

@ -0,0 +1,4 @@
RewriteEngine On
RewriteRule . index.php [END]
RewriteRule . - [END,R=404]

1
hook/404.html Normal file
View File

@ -0,0 +1 @@
0000404

105
hook/index.php Normal file
View File

@ -0,0 +1,105 @@
<?php
require_once(__DIR__.'/../Autoloader.php');
/**
*
* Script hook\index.php
*
* @copyright MX STAR LLC 2022
* @version 4.0.0
* @author Pavel Dmitriev
* @license GPLv3
*
**/
use fox\request;
use fox\moduleInfo;
use fox\foxException;
use fox\foxRequestResult;
$request = request::get();
try {
if ($request->module !== 'hook') {
throw new foxException("Invalid request",400);
}
$request->shift();
$modules=moduleInfo::getAll();
if (!array_key_exists(request::get()->module, $modules)) {
throw new foxException("Invalid module",404);
}
$request->shift();
$modNS=$modules[request::get()->instance]->namespace;
$className=$modNS."\\".request::get()->module;
if (!class_exists($className)) {
throw new foxException("Not found",404);
}
if(!is_a($className, fox\webhookCallable::class,true)) {
throw new foxException("Not found",404);
}
$hookMethod=fox\common::clearInput($request->method,"A-Z");
$hookFunction=fox\common::clearInput($request->function,"a-zA-Z0-9");
$hookXFunction=empty($request->parameters[0])?NULL:fox\common::clearInput($request->parameters[0],"a-zA-Z0-9");
$hookCallMethod="HOOK_".$hookMethod."_".$hookFunction;
$hookXCallMethod="HOOKX_".$hookMethod."_".$hookXFunction;
$hookZCallMethod="HOOK_".$hookMethod;
if (method_exists($className, $hookCallMethod)) {
$rv=$className::$hookCallMethod($request);
} else if (($hookXFunction!==null) && method_exists($className, $hookXCallMethod)) {
$rv=$className::$hookXCallMethod($request);
} else if (method_exists($className, $hookZCallMethod)) {
$rv=$className::$hookZCallMethod($request);
} else if (method_exists($className, "HOOKCall")) {
$rv=$className::hookCall(request::get());
} else {
throw new foxException("Method not allowed", 405);
}
if ($rv!==false) {
ob_clean();
header('Content-Type: application/json; charset=utf-8');
foxRequestResult::throw("200", "OK", $rv);
}
} catch (fox\foxRequestResult $e) {
ob_clean();
header('Content-Type: application/json; charset=utf-8');
header('HTTP/1.0 '.$e->getCode().' '.$e->getMessage(), true, $e->getCode());
if ($e->retVal===null) {
print json_encode(["status"=>$e->getMessage()]);
} else {
print json_encode($e->retVal);
}
exit;
} catch (fox\foxException $e) {
if (($e->getCode()>=400 && $e->getCode()<500) || ($e->getCode() == 501) || ($e->getCode() >= 600 && $e->getCode()<900)) {
trigger_error($e->getStatus().": ".$e->getCode().": ".$e->getMessage()." in ".$e->getFile()." at line ".$e->getLine(), E_USER_WARNING);
print(json_encode(["error"=>["code"=>$e->getCode(),"message"=>$e->getMessage(), "xCode"=>$e->getXCode()]]));
if ($e->getCode()>=400 && $e->getCode()<502) {
header('HTTP/1.0 '.$e->getCode().' '.$e->getMessage(), true, $e->getCode());
} else {
header('HTTP/1.0 501 '.$e->getMessage(), true, $e->getCode());
}
} else {
trigger_error($e->getStatus().": ".$e->getCode().": ".$e->getMessage()." in ".$e->getFile()." at line ".$e->getLine(), E_USER_WARNING);
print(json_encode(["error"=>["errCode"=>500,"message"=>"Internal server error","xCode"=>$e->getXCode()]]));
header('HTTP/1.0 500 Internal server error', true, 500);
}
exit;
} catch (Exception $e) {
trigger_error($e->getCode().": ".$e->getMessage()." in ".$e->getFile()." at line ".$e->getLine(), E_USER_WARNING);
print(json_encode(["error"=>["errCode"=>500,"message"=>"Internal server error", "xCode"=>"ERR"]]));
header('HTTP/1.0 500 Internal server error', true, 500);
throw($e);
}
exit;
?>

View File

@ -105,6 +105,20 @@ export function exec(requestType, method , data, onSuccess,noblank,onError,versi
});
}
export function getFileByToken(token, fileName) {
let url='/api/v2/core/file/'+token;
const a = document.createElement('a')
a.href = url
if (fileName != undefined) {
a.download=fileName;
}
document.body.appendChild(a)
a.click()
document.body.removeChild(a);
}
export class settings {
static async load() {
if (sessionStorage.getItem("baseSettings")==undefined) {

View File

@ -34,7 +34,7 @@ function reloadInstalled() {
$("<td>",{class: "", text: mod.name}).appendTo(row);
$("<td>",{class: "", text: mod.enabled}).appendTo(row);
$("<td>",{class: "", text: mod.instanceOf}).appendTo(row);
$("<td>",{class: "", text: mod.modVersion}).appendTo(row);
$("<td>",{class: "", text: mod.modVersion+"."+mod.modBuild}).appendTo(row);
$("<td>",{class: "", text: mod.title}).appendTo(row);
$("<td>",{class: "", text: UI.stamp2date(mod.installDate,true)}).appendTo(row);
$("<td>",{class: "", text: UI.stamp2date(mod.updateDate,true)}).appendTo(row);
@ -157,7 +157,7 @@ function reloadAvail() {
let row=$("<tr>",{}).bind('contextmenu', mlaContextMenuOpen).addClass("contextMenu").attr("modName",mod.name).attr("modTitle",mod.title);
$("<td>",{class: "idx", text: i}).appendTo(row);
$("<td>",{class: "", text: mod.name}).appendTo(row);
$("<td>",{class: "", text: mod.modVersion}).appendTo(row);
$("<td>",{class: "", text: mod.modVersion+"."+mod.modBuild}).appendTo(row);
$("<td>",{class: "", text: mod.title}).appendTo(row);
$("<td>",{class: "", html: mod.singleInstanceOnly?'<i class="far fa-check-square"></i>':""}).appendTo(row);
$("<td>",{class: "", text: mod.instancesCount}).appendTo(row);

View File

@ -42,6 +42,7 @@ export var langItem={
open: "Открыть",
dialodAddButton: "Добавить",
dialodCreateButton: "Создать",
dialodSaveButton: "Записать",
dialodInfoTitle: "Информация",
dialogRegisterButton: "Регистрация",
dialogRecoveryButton: "Восстановить",
@ -69,6 +70,7 @@ export var langItem={
confCodeFmtErr: "Неверный формат кода подтверждения",
recoverFormEmtyX: "Должно быть заполнено одно из полей",
oauthLoginFailedUNR: "Пользователь не зарегистрирован в системе. Для продолжения работы нужно зарегистрироваться на начальной странице.",
oauthLoginFailed500: "Сервер авторизации вернул неверные данные либо недоступен. Повторите попытку через некоторое время",
type: "Тип",
types: "Типы",
company: "Компания",

View File

@ -39,16 +39,16 @@ export function load() {
} else {
// auth
API.exec("POST", "auth/oauth",{hash: hash, code: code},loginSuccessCallback,false,function onFail(json) {
console.log(json);
let buttons={};
buttons["OK"]=function() {
UI.closeDialog("dialogInfo");
load();
}
if (json.status.code==401 && json.data.error.xCode=="UNR") {
let buttons={};
buttons["OK"]=function() {
UI.closeDialog("dialogInfo");
load();
}
UI.showInfoDialog(langPack.core.iface.oauthLoginFailedUNR,langPack.core.iface.loginFailedDialogTitle,buttons);
} else {
console.log("Error:",json);
UI.showInfoDialog(langPack.core.iface.oauthLoginFailed500,langPack.core.iface.loginFailedDialogTitle,buttons);
}
});
}

View File

@ -34,6 +34,7 @@
--mxs-switch-off: rgba(00,64,128,0.2);
--mxs-switch-disabled: rgba(50,50,50,1);
--mxs-switch-blocked: rgba(255,111,0,0.8);
--mxs-switch-warn: var(--mxs-switch-blocked);
--mxs-switch-alert: rgba(255,35,0,0.8);
--mxs-title-font: 'Jura', sans-serif;