Archiv pro rubriku: Programování

Do CzechIdM umíme připojit eSpis

U našeho zákazníka jsme pomocí CzechIdM plně nahradili standardní grafické rozhraní pro správu eSpisu. Napojením eSpisu k CzechIdM umožňujeme našim zákazníkům  ruční správu jednotlivých entit v eSpisu, stejně tak jako plnou integraci se stávajícími systémy v organizaci. Napojení je realizováno tak, že CzechIdM v eSpisu spravuje:

  • organizační útvary,
  • funkční místa,
  • uživatelské účty a jejich vazby na další entity.

V tomto článku si ukážeme, jak jsme se poprali s grafickou formou rozhraní pro správu jednotlivých entit. Continue reading

Prezentace Scala vs. Java 8

V rámci interních školení u nás proběhla prezentace představující programovací jazyk Scala a porovnávající jej s jazykem Java ve verzi 8. Scala je programovací jazyk kompatibilní s Javou, který v sobě kombinuje objektově orientované a funkcionální programování. Tento jazyk, navržený v roce 2003 Martinem Oderskym, německým počítačovým vědcem působícím ve Švýcarsku, se vyznačuje několika zajímavými vlastnostmi jako jsou pattern matching, duck typing či anonymní funkce. Právě posledně jmenované Anonymní funkce přinesla i Java 8 v podobě lambda výrazů.

Continue reading

CzechPAM: Schvalovací úkoly a úprava dat

Náš nový produkt CzechPAM pomáhá při správě privilegovaných účtů. Má mnoho skvělých vlastností. Jednou z nich je možnost, aby vytvořené úkoly, například přidání uživatele do nějaké skupiny, schvalovali určení uživatelé. Tato vlastnost CzechPAM přidává důležitou kostičku do promyšleného bezpečnostního mechanismu našeho produktu. O tom, jak jsme schvalovací úkoly přenesli do CzechPAM, se dozvíte dále v článku.

Continue reading

CzechIdM Roadmap 2015

Každý větší software musí mít nejakou vizi, cíl, kam chce směřovat. Bez vize je jeho vývoj pouze nabalující se sněhovou koulí. Vývoj běží na úrovni implementace vlastní setrvačností. V začátcích nám samovolný vývoj nevadí, ale v momentě, kdy aplikace dosáhne určitého rozsahu, je takový způsob práce dále neudržitelný.

S příchodem roku 2015 jsme tedy i my formalizovali naši vizi a vytvořili pro CzechIdM Roadmapu.
CzechIdM je ve vývoji již zhruba pět let, což je relativně dlouho. Většina vývoje je přizpůsobování produktu na míru konkrétnímu zákazníkovi. Dostatečně obecné (a užitečné) funkcionality jsou zároveň zařazovány do standardního produktu. Vývoj je tedy z větší části řízen poptávkou po konkrétních funkcionalitách, z části menší pak vývojem vlastních features „do šuplíku“.

Continue reading

Jarní osvěžení s Mockitem

Zima už je, zdá se, definitivně za námi, a tak, stejně jako budeme v následujících dnech a měsících postupně odkládat všechny ty těžké bundy a kabáty,  můžeme odhodit všechno to „heavyweight“ programování a ulehčit si psaní testů s testovacím frameworkem Mockito.

Jistě se již každý z vás někdy ocitl v situaci, kdy je postaven před úkol otestovat metodu, která například při svém běhu načítá data z databáze nebo z internetu. Problém s testováním takového kódu je ten, že pro správný průběh testu musíte mít v databázi připravena testovací data a přístup k těmto datům je obecně řádově pomalejší, než bychom potřebovali.

Uvažujme například následující blok kódu v jazyku Java:

public boolean validateUniqueEmail(String email,EntityManager em){
     TypedQuery<String> q = em.createQuery("SELECT u.email FROM User u", User.class);
     List<String> result = q.getResultList();
     return !result.contains(email);
}

Asi není třeba vysvětlovat, co tato metoda má dělat, avšak pokud byste chtěli testovat správné chování této metody, pak buď musíte připravit v databázi testovací data (pak ale stejně vyvstává otázka, kde vzít instanci EntityManagera), nebo vytvořit vlastní mock pomocí dědění od třídy EntityManager a ten poté vložit jako parametr metody.

Právě s tím Vám pomůže Mockito. V následujícím bloku kódu se nachází testovací metoda, která využívá právě mock EntityManagera, vytvořený Mockitem.

 

public void testValidateEniqueEmail(){
    List<String> testData = new ArrayList<>();
    testData.add("email@somewhere.org");
    testData.add("email@somewhereelse.com");

    // Vytvoreni mock objektu
    EntityManager mockEM = mock(EntityManager.class);
    TypedQuery mockQuery = mock(TypedQuery.class);

    // Definovani chovani mock objektu
    when(mockQuery.getResultList()).thenReturn(testData);
    when(mockEM.createQuery(anyString(),String.class)).thenReturn(mockQuery);

    Assert.assertTrue(validateUniqueEmail("email@unknown.edu",mockEM));
    Assert.assertFalse(validateUniqueEmail("email@somewhere.org",mockEM));
}

Jak je patrné z ukázky, vytváření mock objektů je s Mockitem velice snadné. V tomto příkladu jsme definovali testovací data a poté jsme určili chování mock objektů tak, aby při dotazu tato data byla vrácena jako výsledek. Za zmínku zde stojí použití anyString(), což je v terminologii Mockita takzvaný matcher a zastupuje jakoukoli instanci třídy String (samozřejmě lze definovat i vlastní matchery).

Závěr

V tomto článku jsme si ukázali naprosto základní použití frameworku Mockito. Toto je však pouze zlomek toho, co tento nástroj umí a pokud vás tento článek zaujal a chcete se dozvědět více, pak navštivte stránky http://mockito.org/  nebo mě neváhejte kontaktovat na emailové adrese info@bcvsolutions.eu.

Automatické přidělování rolí

Role v CzechIdM představují balík oprávnění identity ke koncovému systému nebo přímo CzechIdM (admin role). Role lze přiřazovat ručně, například pomocí webového GUI, nebo pomocí workflow a pravidel. Mým úkolem bylo implementovat nový způsob přiřazování rolí – automatické přidělování rolí, které lze použít v průběhu standardní rekoncilace/synchronizace. Dále se v článku dozvíte, jak byla tato nová funkcionalita v CzechIdM implementována a jak automatické přidělování rolí konfigurovat a spouštět.

Automatické role

Nejprve je třeba definovat, co jsou to automatické role. Nově v CzechIdM mají všechny role, včetně admin rolí, atributy, které slouží primárně pro rozhodování, komu danou roli při automatickém přiřazení přidělit. Počet těchto atributů je napevno stanoven na 10 a jsou jednoduše pojmenovány attribute1 až attribute10. Role je automatická právě tehdy, když má vyplněný alespoň jeden tento atribut – hodnota atributu není null a nebo prázdný řetězec.

Pro přiřazování automatických rolí byly definovány 4 nové užitečné metody nad třídou Data.

  • GetAutoRoles() – metoda používá aktivní UserView a pro daného uživatele vrací seznam automatických rolí, které splňují podmínky přidělení v příslušných atributech rolí.
  • AssignAutoRolesExpUsers() – na základě explicitně předaného seznamu uživatelů těmto přiřadí odpovídající automatické role.
  • AssignAutoRolesExpRoles() – na základě explicitně předaného seznamu rolí vyhledá odpovídající uživatele a těmto přiřadí odpovídající automatické role.
  • AssignAutoRoles() – pro všechny uživatele v systému přiřadí odpovídající automatické role.

Pomocí těchto metod lze například ve workflow nebo pravidlech jednorázově přiřadit automatické role uživatelům.

Konfigurace automatického přiřazení rolí

Předtím, než je možné přikročit k samotnému procesu automatického přidělování, je potřeba provést konfiguraci podmínek, které musí uživatelé splňovat, aby jim byla automatická role přidělena.

Podmínky je možné si představit jako trojici:

Atribut:Relace:Hodnota atributu

  • Atribut je název uživatelského atributu, například „tel“
  • Relace je vztah mezi názvem a hodnotou atributu, v současnosti lze vybírat z equal (EQ), not equal (NEQ) a like (LIKE)
  • Hodnota atributu je vypovídající, například „+420 540 123 456“

Konfigurace podmínky probíhá ve dvou krocích. První část konfigurace je umístěna v pravidle getAutoRolesAttributes. Pravidlo tedy editujeme, nejjednodušeji pomocí webového rozhraní. Pravidlo getAutoRolesAttributes obsahuje definici DTO, které představuje první část podmínky pro automatické přidělování rolí. Pravidlo má následující strukturu:

DTO roleAttrs = new DTOGroup();
 DTO attr1 = new DTOGroup();
 attr1.put(Constants.AUTO_ROLE_ATTRIBUTE_NAME, "tel");
 attr1.put(Constants.AUTO_ROLE_ATTRIBUTE_RELATION, RoleAttrRelation.EQ);
 …
 DTO attr10 = new DTOGroup();
 attr10.put(Constants.AUTO_ROLE_ATTRIBUTE_NAME, "");
 attr10.put(Constants.AUTO_ROLE_ATTRIBUTE_RELATION, RoleAttrRelation.EQ);
 …
 roleAttrs.put(AutoRoleAttr.EQ.name(), RoleAttrEq.OR);

Na řádku attr1.put(Constants.AUTO_ROLE_ATTRIBUTE_NAME, "tel"); jako druhý argument funkce attr1.put() vyplníme název atributu uživatele, například „tel“. Na následujícím řádku se vyplňuje Relace, v příkladu je RoleAttrRelation.EQ, který reprezentuje rovnost řetězců. Jak je vidět, lze nastavit až 10 podmínek pro přidělování rolí, přičemž logický operátor mezi jednotlivými podmínkami je ve výchozím stavu OR, jak je vidět na posledním řádku z příkladu.

Nyní máme tedy nastavenou první část podmínky Atribut:Relace:Hodnota a chybí nám ještě poslední část samotná hodnota atributu. Tu nastavujeme do již představených atributů rolí Attribute1 až Attribute10, přičemž pořadí atributů odpovídá řádkům v pravidle getAutoRolesAttributes. Toto můžeme nastavit pomocí webového GUI:

auto_config

Nyní máme již nastavené podmínky pro automatické přidělování rolí. Spuštění automatického přidělování rolí může probíhat automaticky při standardní rekoncilaci/synchronizaci. Pomocí GUI lze nastavit, kdy bude tato funkcionalita aktivní.

synch_conf

Omezení

Je třeba zdůraznit, že automatické přidělení rolí proběhne pouze při změnách uživatelské identity (update, vytvoření). Tedy vždy pouze pro určité stavy a akce. Jsou to tyto:

  • Assigned – UPDATE_IDENTITY
  • Matched – LINK_ACCOUNT_AND_UPDATE
  • Missing identity – CREATE_IDENTITY
  • Missing identity – CREATE_IDENTITY_AND_UPDATE

Automatické role mají určitá omezení, která je třeba mít na paměti při konfigurování. Zejména lze přiřazovat role pouze na základě atributů datového typu String. Například nelze použít pouze název atributu „homeOrganisation“ nebo „disabled“.

Dále je třeba počítat s tím, že jednotlivé podmínky jsou vzájemně spojeny logickým operátorem OR. Návrh automatických rolí počítá s pozdějším přidáním dalších logických operátorů jako je AND, nicméně v současné podobně je jedinou volbou OR.

Závěr

V tomto článku jsem představil novou vlastnost CzechIdM – automatické přiřazování rolí. Byly definovány automatické role, ukázány možnosti konfigurace automatického přiřazení rolí a také omezení pro tuto novou funkcionalitu. Možnosti využití jsou jistě širší, než je schopen pojmout tento článek, proto se můžete těšit na pokračování, kde budou představeny širší možnosti konfigurace automatických rolí. Pro jakékoli dotazy mě můžete kontaktovat na info@bcvsolutions.eu.

Kontrolované a zakázané organizace v CzechIdM

V tomto článku vás seznámím s novou funkcionalitou v CzechIdM, která umožňuje snazší implementaci centrální správy uživatelů ve společnostech se složitější organizační strukturou, což byl případ jednoho z našich zákazníků. Jedná se o funkcionalitu zakázaných organizací. Ta umožní vyjmout ze správy administrátora nějakou organizaci, ačkoliv ten samý administrátor spravuje její nadřazenou organizaci.

Centrální a lokální administrátoři

Do administrátorského rozhraní mají přístup všichni uživatelé, kteří mají v CzechIdM přidělenou nějakou roli typu „ADMIN“. Každá taková role přiřazuje svým držitelům rozličná oprávnění (čtení či editace uživatelů, rolí, systémů,…) na základě konkrétního nastavení. Ovšem aby mohl administrátor spravovat uživatele v CzechIdM, musí navíc kontrolovat tu organizaci, v níž je uživatel zařazen (tj. uživatelovu domovskou organizaci). Podobně je to i s organizacemi; administrátor může spravovat jen ty organizace, které kontroluje.

Administrátorovi je tedy možné nastavit buď plná práva na všechny uživatele, nebo vytvořit lokálního administrátora, který kontroluje pouze určitou část organizační struktury (jednu nebo více organizací). Organizační struktura je stromová; mluvíme tedy o nadřazených a podřazených organizacích, přičemž celý organizační strom má v CzechIdM jeden kořen – organizaci „top“.

Pokud administrátor kontroluje organizaci „top“, znamená to, že vidí všechny uživatele a organizace. Příkladem takového uživatele je třeba „admin“, který existuje v každé instanci CzechIdM jako centrální administrátor.

Stromová organizační struktura

Při nastavování kontrolovaných organizací se uplatňuje jednotný princip, že kořen organizačního podstromu reprezentuje celý podstrom. Má-li tedy administrátor nastavenou organizaci „Celá firma“ ve svých kontrolovaných organizacích, znamená to, že kontroluje nejen uživatele umístěné přímo v organizaci „Celá firma“, ale také uživatele z jejích podřazených organizací (např. „Ředitelství“, „Odbor účetnictví“,…).

Tento princip by v ideálním případě stačil k tomu, aby se dala nastavit libovolná potřebná kombinace kontrolovaných organizací pro administrátory. Nicméně reálné situace zřídka bývají ideální a někdy může být potřeba, aby administrátor měl možnost spravovat nadřazenou organizaci, ale ne všechny její podřazené organizace. Například aby mohl spravovat uživatele v organizaci „Celá firma“, ale ne uživatele v její podorganizaci „Ředitelství“. Ideální řešení by bylo upravit organizační strukturu či umístění uživatelů v ní tak, aby takovéto výjimky neexistovaly. To však samozřejmě není vždy možné. Proto vznikly tzv. zakázané organizace.

Zakázaná organizace je výjimka z kontrolovaných organizací. V příkladu uvedeném výše jednoduše nastavíme administrátorovi, aby kontroloval „Celou firmu“, ale aby měl zakázané „Ředitelství“. Po přihlášení do CzechIdM pak bude moci spravovat uživatele celé firmy kromě uživatelů z organizace „Ředitelství“ a jejích podřazených organizací; tyto uživatele ani neuvidí ve výpisu uživatelů.

Kontrolované a zakázané organizace

Kontrolované a zakázané organizace

Bezpečnost

Při implementaci zakázaných organizací bylo třeba důkladně promyslet všechny možnosti. Je zásadní, aby uživatel, kterému je tímto způsobem zakázán přístup ke správě části organizační struktury, skutečně nemohl žádným způsobem ovlivnit její nastavení. To znamená:

  • nesmí mít možnost vytvářet, editovat či mazat uživatele z nějaké své zakázané organizace
  • uživatelům, které má ve správě, nemůže nastavit, aby kontrolovali nějakou organizaci, které leží mimo jeho vlastní kontrolu (ať už přidělením kontrolované organizace, nebo odebráním zakázané organizace)
  • uživatelům, které má ve správě, nemůže odebrat kontrolu nad organizací, která leží mimo jeho vlastní kontrolu (ať už odebráním kontrolované organizace, nebo přidáním zakázané)

Při administraci tedy může dojít třeba k následující situaci:

  1. Administrátor kontroluje organizaci „top:Celá firma“ a má zakázanou organizaci „top:Celá firma:Ředitelství“.
  2. Uživatel je zařazen v organizaci „top:Celá firma“ (takže ho administrátor může spravovat) a na začátku nekontroluje žádnou organizaci.
  3. Administrátor nastaví do kontrolovaných organizací uživatele organizaci „top:Celá firma“.
  4. Uživatel má ve výsledku kontrolu nad organizací „top:Celá firma“, ale má zakázanou organizaci „top:Celá firma:Ředitelství“. Tuto zakázanou organizaci totiž přidalo automaticky CzechIdM při uložení změn nad uživatelem. To proto, že administrátor nesmí nikomu nastavit kontrolu nad „Ředitelstvím“, které sám nekontroluje.

Obdobně se CzechIdM chová i při odebírání kontrolovaných organizací či přidávání/odebírání zakázaných organizací.

Kontrola oprávnění administrátora se provádí na úrovni GUI i v backendu. Na úrovni GUI lze měnit přiřazení kontrolovaných a zakázaných organizací pouze v tom rozsahu, na který má administrátor oprávnění. Pokud by chtěl nastavit uživateli takovou kombinaci, která by vedla k nesmyslnému stavu (např. kontrolovaná organizace by ležela pod zakázanou organizací), CzechIdM by neumožnilo nastavení uložit.

V backendu se kontroluje oprávnění vždy v rámci metody Data.checkinView (což je jediná metoda, přes kterou lze uložit tzv. view – „pohled na uživatele“ – do databáze). Ani při vynechání kontroly oprávnění v GUI tedy nelze nastavit nic, co by bylo v rozporu s oprávněními přihlášeného uživatele.

Závěr

V tomto článku jsem vám představila jednu z nových vlastností CzechIdM. Pokud by vás zajímaly detaily jejího chování či podrobnosti implementace, neváhejte mi napsat na e-mail info@bcvsolutions.eu.

CzechPAM: Jak bezpečně ukládat data

Naším cílem bylo udělat CzechPAM jakožto bezpečnostní aplikaci. To samozřejmě zahrnuje držení všech důležitých dat v zašifrované podobě. Bohužel i v dnešní době je stále běžné u aplikačních serverů ukládat spojení k databázi (Datasource) v plaintextové podobě. O tom, jak jsme se s tímto úkolem poprali a jak je realizované bezpečné ukládání dat v CzechPAMu, se dozvíte dále v článku.

czechpam_lock

Continue reading

Export dat do XLS a CzechIdM

Pro našeho klienta jsme implementovali export vybraných atributů identit do excelovského formátu XLS, jelikož otevírání CSV v tabulkovém editoru může být komplikované především z důvodu nastavení oddělovacího znaku jednotlivých sloupců. Dalším omezením je editace vzhledu výsledného exportu, s čímž si tabulkový editor pro CSV neporadí. V tomto článku si tedy ukážeme základní kroky nutné pro práci se soubory XLS pomocí frameworku Apache POI.

Apache POI

POI je javovský framework vydávaný Apache Software Foundation, takže stejně jako u ostatních produktů Apache má otevřený zdrojový kód a je vydáván pod svobodnou licencí. Současnou stabilní verzí je 3.11. Obecně se tento framework snaží poskytovat API pro standardní sadu dokumentů společnosti Microsoft, nejde tedy jen o práci se soubory typu XLS nebo XLSX, na které se v článku zaměřujeme, ale i o wordovské dokumenty nebo prezentace.

Závislosti a příprava Eclipse

Všechny třídy, které budeme pro práci s frameworkem potřebovat, jsou obsaženy v jediném JARu, nazvaném poi.jar. Tento archiv potřebuje další knihovny, ale my bude pro využití v CzechIdM potřebovat přidat pouze jedinou, a to commons-codec.jar z adresáře /lib. Ostatní, například log4j, už IdM obsahuje.

Pokud používáme k buildu projektu Ant, musíme knihovny ručně přidat do classpath. CzechIdM je distribuováno jako enterprise archiv, a jednotlivé moduly – tedy WAR a EJB – na tomto archivu závisejí. Proto pro přídání nových knihoven do classpath stačí nakopírovat knihovny do adresáře /lib projektu CzechIdM-ear. Jak WAR tak i EJB mají tento EAR v build path a můžou využívat jeho knihovny.

Pro build mavenem musíme přidat závislost do souboru pom.xml projektu CzechIdM-parent. V něm uvedeme kompletní element i s verzí, v EJB se na něj pouze odkazuje. Pro CzechIdM-parent/pom.xml:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.10-FINAL</version>
</dependency>

Pro CzechIdM-ejb/pom.xml:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
</dependency>

Tím máme nastaveny projekty a můžeme se přesunout na samotné generování XLS.

Tvorba XLS

Samotné vytváření souboru je vlastně kopie jeho struktury, kterou ale musí programátor napsat ručně. Nejprve vytvoříme workbook a sešit, do kterých chceme zapisovat. Nastavíme font a vytvoříme styl buněk tabulky. Jednotlivé řádky sešitu vytvoříme zavoláním metody createRow(int) na aktuálním sešitu. Právě parametr metody zde vytváří poněkud nepříjemnou vlastnost, a to že pokud si nedáme pozor na číslování, můžeme si přepisovat data. Stejná vlastnost bohužel platí i pro sloupce řádků. Následující ukázka kódu zachycuje vytvoření jednoduchého souboru XLS:

public static void main(String[] args) throws Exception {
    FileOutputStream out = new FileOutputStream("workbook.xls");
    // create a new workbook
    Workbook wb = new HSSFWorkbook();
    // create a new sheet
    Sheet s = wb.createSheet();
    // set the sheet name in Unicode
    wb.setSheetName(0, "export");

    // create workbook font
    // set font 1 to 12 point type
    // make it bold
    // arial is the default font
    Font f = wb.createFont();
    f.setFontHeightInPoints((short) 12);

    // create cell style
    CellStyle cs = wb.createCellStyle();
    cs.setFont(f);
    cs.setWrapText(true);
    cs.setDataFormat(wb.createDataFormat().getFormat("#,##0.0"));

    // create row and cells
    Row r = s.createRow(0);
    for (int i = 0; i < 5; i++) {
        Cell c = r.createCell(i);
        // set cell style and its value
        c.setCellStyle(cs);
        c.setCellValue("Column no. : " + i);
    }

    // write workbook to output stream
    wb.write(out);
    out.close();
}

Nepříjemnosti s typováním

Jelikož potřebujeme generovat soubory dynamicky a formát výsledného XLS není natvrdo zadrátovaný v kódu, je potřeba zpracovávat všechny vstupní hodnoty jako Object. POI ale přijímá jako vstupní parametry pouze řetězce, datumy a čísla. Samozřejmě se opět můžeme ke všem vstupům chovat stejně a volat Object.toString(), ale to nás dostane k problémům s formátováním. Zkuste si to například pro datum nebo List, s výsledkem ale nejspíše spokojeni nebudete. Toto omezení frameworku vede na kód z následující ukázky:

protected void writeAttribute(Object value) {
    /* … */
    // handle format of collections
    if (Collection.class.isAssignableFrom(value.getClass())) {
        Collection<?> collection = (Collection<?>) value;
        if (collection.isEmpty()) {
            cell.setCellValue("");
        } else {
            // separate list elements by new lines
            StringBuilder sb = new StringBuilder();
            for (Object object : collection) {
                sb.append(object).append("\n");
            }
            sb.deleteCharAt(sb.length() - 1);
            cell.setCellValue(sb.toString());
        }
    }
    /* … */
}

Pokud tedy chceme zapisovat různorodé objekty a jejich návratová hodnota metody toString() se neschoduje s požadovaným formátem XLS, musíme si pro každý formát napsat handler.

Ukončení zápisu

Zapsat výsledný workbook je velice jednoduché – třída Workbook poskyje metodu write(OutputStream), jejíž zavolání přesměruje všechna data do poskytnutého streamu. Nejsme tedy omezeni například na zápis pouze do souboru na disk, což nám v IdM přijde vhod. Zapisovací metodu „nakrmíme“ třídou ByteArrayOutputStream, z níž pohodlně dostaneme data jako pole bytů. Jakmile pak uživatel využije možnosti exportu XLS, vyskočí mu v prohlížeči nabídka na stažení sešitu a nepotřebuje nic hledat. Ukázku výsledného souboru si můžete prohlédnout na obrázku níže.

Ukázka výsledného XLS.

Ukázka výsledného XLS.

Závěr

Možnosti Apache POI jsou velmi široké a tento článek pokryl pouze jejich malou část. Pokud vytváříte aplikace, jejichž části musí být použitelné v kancelářském prostředí, je to i přes drobné nedostatky zajisté dobrá volba.

Pokud byste měli nějaké dotazy, neváhejte mě kontaktovat na info@bcvsolutions.eu.