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 metadatas3storage- 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 settingsdelegatestorage- ú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:
<!-- 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-doplainMetadata- titulek, autor, popis, datum, klíčová slovalocalizedMetadata- 1:n vazba na přetížení metadata dle jazyka (nastavení atributemlocalizedMetadataTable)relatedTags- 1:n vazba na štítky (nastavení atributemrelatedTagsTable)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 motivuimportMetadata- rozšíření o identifikátor ze zdrojového systémustagePublish- 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_TX | Povolené 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*, overlaps | Povolené, 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ý sloupec | Ve 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 queryParams | Povolené. 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
<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í
<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 zapisovatcom.fg.webapp.cps.v1.modules.spring.resource.OriginAwareResource- zpřístupňuje původní location resourcecom.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.Filekonkrétně implementacecom.fg.webapp.cps.v1.modules.spring.resource.CpsModuleStorageFilezí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)
storage:storageId:/pathPříklad
storage:f.file:/2020/07Vý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
publishStorage:storageId:/pathVyhodnocení 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
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); }
}