S3 files, unAuth methods in API, bugifx
s3 files download, packages version fix, oauth fix, time intervals,
This commit is contained in:
parent
872da83978
commit
f59fdf61ca
|
@ -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"
|
|
@ -6,3 +6,4 @@
|
|||
/composer.lock
|
||||
/vendor
|
||||
.scannerwork
|
||||
.vscode
|
||||
|
|
|
@ -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 - позволяет с минимальными затратами но при этом со всеми необходимыми проверками организовать доступ к нужным сущностям.
|
||||
|
|
58
api/v2.php
58
api/v2.php
|
@ -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,23 +58,45 @@ 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);
|
||||
}
|
||||
|
||||
foxRequestResult::throw("200", "OK", $rv);
|
||||
$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);
|
||||
}
|
||||
|
||||
} catch (fox\foxRequestResult $e) {
|
||||
ob_clean();
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -117,6 +157,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace fox;
|
|||
/**
|
||||
*
|
||||
* Class fox\common
|
||||
* Common methods ex. generation GUID
|
||||
*
|
||||
* @copyright MX STAR LLC 2021
|
||||
* @version 4.0.0
|
||||
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
|
@ -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"
|
||||
],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -28,5 +28,9 @@ interface objectStorageClient
|
|||
public function createBucket($bucket);
|
||||
|
||||
public function deleteBucket($bucket);
|
||||
|
||||
public function headBucket($bucket);
|
||||
|
||||
public function headObject($bucket, $key);
|
||||
}
|
||||
?>
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -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);
|
||||
*
|
||||
*/
|
||||
|
||||
}
|
||||
?>
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
RewriteEngine On
|
||||
RewriteRule . index.php [END]
|
||||
RewriteRule . - [END,R=404]
|
||||
|
|
@ -0,0 +1 @@
|
|||
0000404
|
|
@ -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;
|
||||
|
||||
?>
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: "Компания",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue