Edee.one · Dokumentace

Storage

konfigurace

Možné typy úložišť - základní implementace

  • filesystemstorage - složka na disku, rychlé, nepodporuje přístup dle id, bez možnosti dotazování dle query (se StorageService lze dotazovat pouze dle path)
  • databasestorage - na základě db tabulky, co úložiště to vlastní db tabulka, id souborů unikátní v rámci této tabulky

Doplňkové implementace

  • publishstorage - kombinace lokalni filesystem storage pro uložení obsahu souborů na disku s db implementací pro metadata
  • s3storage - kombinace db implementace pro uložení metadat a S3 úložištěm pro obsah souborů (pro služby jako Amazon S3, DigitalOcean Spaces, Linode Object Storage, Cloudflare R2)
  • segmentedstorage - lze vytvářet virtuální úložiště, které jsou jen výsekem hlavního (používá se pro důsledné oddělení prostoru pro ms s vlasním oprávněním)
  • combinedstorage - umožňuje kombinované úložiště, které ukládá data na jedno z nastavených úložišť dle nastavení upload settings
  • delegatestorage - úložiště, které veškeré požadavky pouze deleguje na jiné (pro speciální případy, např. s vlastní implementací storage)

Storage traits

Od v10 je db struktura rozšiřitelné dle konfigurace traits. Je nutné nastavit adam implementace (atributem dialect) - tedy:

xml
<!-- uzivatelsky vkladany verejny obsah (polozka soubory v administraci) -->  <publishstorage id="f.file"   dialect="adam"  name="Soubory" normalize="true" fileVariantThreadPool="image"    path="/data/storage-file" url="${edee.context}/file/edee/" trash="/trash"    table="STORAGE_FILE" connection="c.edee"    localizedMetadataTable="STORAGE_FILE_METADATA"    relatedTagsTable="EDEE_TAG_FILE_REL"  traits="plainMetadata,expirableMetadata,stagePublish,motiveMetadata,importMetadata"      secured="true"/>

Existující traits:

  • expirableMetadata - platnost od-do
  • plainMetadata - titulek, autor, popis, datum, klíčová slova
  • localizedMetadata - 1:n vazba na přetížení metadata dle jazyka (nastavení atributem localizedMetadataTable)
  • relatedTags - 1:n vazba na štítky (nastavení atributem relatedTagsTable)
  • publishMetadata - původní podoba, tedy včetně custom1-custom9, platnosti, a základních metadat (na nových projektech je vhodnější nahradit dle potřeby za plainMetadata ...)
  • motiveMetadata - přiřazení obrázku ze stejného úložiště jako motivu
  • importMetadata - rozšíření o identifikátor ze zdrojového systému
  • stagePublish - povolí publikace souboru do stage modulu (v kombinaci s nastavením stage modulu)

Kompatibilita dotazů po opravě SQL injection

Validace má dvě nezávislé vrstvy. REST endpointy pro CMS, Stage, hodnocení a přesměrování, AI nástroje Stage a výpisy administrace řazené podle parametru z requestu (správce souborů, přesměrování) kontrolují klientský query/filter a order pomocí UntrustedQueryValidator. Klasické JDBC úložiště (dialect="mysql" nebo dialect="oracle", včetně optimize="read") navíc kontroluje názvy sloupců ve všech dotazech, tedy i v dotazech z Java kódu, konfigurace, fulltextu a administrace. ADAM úložiště (dialect="adam") používá vlastní mapování metadat; níže uvedené přepínače se na něj nevztahují.

PoužitíChování po opravěPřípadná úprava aplikace
eq(name,...), like(path,...), asc(id), exact(name,...)Beze změny, hodnoty se předávají jako SQL parametry.Žádná.
Logické názvy, např. path, lang, priority, a fyzické sloupce, např. PATH_TX, CUSTOM2_TXPovolené v JDBC úložišti; porovnávání názvů je bez ohledu na velikost písmen.Používat známý název sloupce nebo logickou vlastnost.
propertyEq, ostatní property*, overlapsPovolené, pokud jsou všechny části představující sloupce známé. Logické názvy se mapují i uvnitř těchto podmínek.Není třeba nahrazovat bezpečná porovnání sloupců za native.
Rozšíření z <publishmetadataproperties>Klíče deklarované na daném JDBC úložišti jsou povolené automaticky.Žádná.
Jiný vlastní fyzický sloupecVe výchozím režimu odmítnutý.Přidat ho do queryAllowedColumns daného úložiště.
SQL funkce v názvu, např. asc(LENGTH(NAME_TX)), nebo native(...)Odmítnuté klientskou validací i výchozí validací JDBC úložiště.Použít QL operátor, např. shortest(name) pro řazení podle délky; vlastní SQL sestavovat na serveru. Pro migraci serverových dotazů lze na konkrétním JDBC úložišti dočasně obnovit původní režim.
when(isTrue(#enabled#),eq(id,1)) s queryParamsPovolené. Podmínka se vyhodnocuje v Javě, SQL větev se kontroluje vždy.Žádná.
Klientský výraz místo názvu, např. eq(#column#,1)Odmítnutý; výrazy v hodnotách, např. eq(id,#id#), zůstávají podporované tam, kde se zpracovávají queryParams.Do názvu poslat konkrétní povolený identifikátor.

Další sloupce bez vypnutí ochrany

xml
<databasestorage id="f.custom" name="Vlastní soubory" dialect="mysql"                 connection="c.edee" table="CUSTOM_STORAGE"                 queryAllowedColumns="EXTERNAL_ID_TX,IMPORT_TIME_DT"/>

queryAllowedColumns je čárkami oddělený seznam dalších fyzických identifikátorů (případně alias.column). Nejde o SQL výrazy ani o mapování logických názvů. Sloupce musí v databázi již existovat; atribut neprovádí změnu schématu. Prázdný seznam je výchozí. Neznámé názvy, funkce a native se nadále odmítají. Seznam platí jen pro dané úložiště a uplatní se ve filtru i řazení. Mezery a prázdné položky (např. koncová čárka) se ignorují.

Dočasné obnovení původního chování

xml
<databasestorage id="f.legacy" name="Starší integrace" dialect="mysql"                 connection="c.edee" table="LEGACY_STORAGE"                 queryValidationMode="warn"/>

queryValidationMode má hodnoty enforce (výchozí) a warn. V režimu warn JDBC úložiště neznámé názvy, funkce a native neodmítne, ale předá je do SQL jako dřív a zapíše varování s ID úložiště a názvem (každou kombinaci jen jednou). Jiná hodnota atributu zastaví načtení konfigurace úložiště. Tím se na tomto úložišti znovu připouští SQL injection, pokud k němu dorazí neověřený dotaz. Režim je určen jen jako dočasný migrační most pro prověřená serverová volání, nikoli jako plošné nastavení všech úložišť. Z logu se zjistí, které dotazy je potřeba upravit; pak režim odstraňte.

Oba atributy se nastavují na <databasestorage> nebo na <publishstorage> s klasickým JDBC dialectem, kde se předají jeho databázové části. Platí i pro optimize="read". Nejde o globální JVM přepínač. Změna se projeví po opětovném načtení konfigurace úložiště (nejjednodušeji restartem aplikace). Při konstrukci úložiště v Javě odpovídají atributům metody DatabaseStorage.setQueryAllowedColumns(...) a setQueryValidationMode(...).

Režim warn nevypíná validaci vstupů od klienta. UntrustedQueryValidator dál odmítá native a SQL funkce v názvech na REST/AI vstupu i ve výpisech administrace, které řadí podle parametru z requestu (filter.orderBy ve správci souborů a ve výpisech přesměrování). Serverové QL parsery zůstávají permisivní; restrikce mimo JDBC úložiště se přidává pouze na výslovně validované vstupy.

Před nasazením projděte vlastní storage dotazy v XML a Java kódu, zejména native, SQL funkce v názvech a vlastní fyzické sloupce. Pro vlastní sloupce preferujte queryAllowedColumns. Po migraci serverových dotazů odstraňte queryValidationMode="warn" a ověřte dotčený fulltext, správce souborů a přílohy aplikace.

java api

Existuje několik cest jak lze využít Storage API.

  • Spring resource - vhodné pro práci s jednotlivými soubory, výhodou je možnost vyměnit za jinou resource implementaci (filesystem, classpath, http url apod.)
  • CpsModuleStorageSupport - základní přístup ke obsahu storage, vhodné pro vytváření položek, importy apod.
  • StorageService - extenze pro lepší práci s uložišti s využítím query, cachování na straně service, zjednodušené dotazování do standardního úložiště (f.file) apod.

Preferovaná cesta od Edee v10 je práce pomocí StorageService

Edee UI support

Standardní úložiště (f.file) je dostupné pomocí správce souborů.

Ve slovníku je připravena stránka (fileBrowserPage) pro vytvoření správce vlastního úložiště.

Práce se Storage API

Přístup pomocí Spring Resource

Preferovaná varianta je pracovat primárně se Spring resource, kde je podpora pro tzv. StorageResource a PublishStorageResource. Pro přístup je nutné použít org.springframework.core.io.ResourceLoader v podobě aplikačního kontextu (stačí využít Autowired)

Obě cesty podporují následující

  • org.springframework.core.io.WritableResource - do resource lze zapisovat
  • com.fg.webapp.cps.v1.modules.spring.resource.OriginAwareResource - zpřístupňuje původní location resource
  • com.fg.webapp.cps.v1.modules.spring.resource.IdProviderResource - pro získání id souboru a úložiště
  • com.fg.webapp.cps.v1.core.store.misc.metadata.FileItemPublishMetadataAccessor - práce s metadaty položky úložiště
  • reprezentaci v podobě souboru java.io.File konkrétně implementace com.fg.webapp.cps.v1.modules.spring.resource.CpsModuleStorageFile získané pomocí Resource#getFile() - je nutné počítat, že podpora file API je pouze částečná

storageResource

Přístup pomocí location ve tvaru (poslední část nemusí být jen path, ale i id)

kód
storage:storageId:/path

Příklad

kód
storage:f.file:/2020/07

Výsledný resource je vlastně jen ukazatelem na úložiště a cestu, která se vyhodnotí při požadavku na data. Opakovaná práce s resourcem, tak může vést k několikanásobnému dotazu na storage api, potažmo na DB.

Zápis do output stream neprobíhá přes upload část storage, takže není prováděna kontrola a zpracování dat definované jako uploadAction, lze ale využít k tomu určené metody (uploadFileContent a getUploadingOutputStream).

publishStorageResource

Vzniká obdobně pomocí location ve tvaru

kód
publishStorage:storageId:/path

Vyhodnocení proběhne v okamžiku vzniku instance resource (pokud sílový soubor neexistuje vzniká MissingPublishStorageResource).

Instance resource drží interně objekt FileItem - soubor nebo adresář úložiště. Zápis pomocí outputStream pracuje s upload metodami API, reálně k akci dochází po uzavření streamu.

Přímý přístup ke Storage API

Pro základní operace lze využít com.fg.webapp.cps.v1.modules.CpsModuleStorageSupport.

Kompletní sada funkcí pro úložiště je pak pomocí třídy com.fg.webapp.cps.v1.core.store.Storage poskytující primárně com.fg.webapp.cps.v1.core.store.FileItem položky.

Instanci lze získat pomocí následující konstrukce

java
import com.fg.webapp.cps.v1.CPS;import com.fg.webapp.cps.v1.core.security.User;import com.fg.webapp.cps.v1.core.store.Storage;import com.fg.webapp.cps.v1.modules.CpsModuleSupport;
public class Test {
    @Autowired    CpsModuleSupport cpsModuleSupport;
    void example() {        String storageId = "f.file";        Storage storage = cpsModuleSupport.getStorageSupport().getStorageById(storageId);
        // pokud je vhodne pracovat s pravy uzivatele, je nutne predat jeho objekt         User user = cpsModuleSupport.getSecuritySupport().getLoggedInUser();        Storage storageWithSecurity = cpsModuleSupport.getStorageSupport().wrapStorageAsSecured(storage,user);    }
}