abclinuxu.cz AbcLinuxu.cz itbiz.cz ITBiz.cz HDmag.cz HDmag.cz abcprace.cz AbcPráce.cz
Inzerujte na AbcPráce.cz od 950 Kč
Rozšířené hledání
×
    dnes 04:44 | Nová verze

    Po roce vývoje od vydání verze 1.24.0 byla vydána nová stabilní verze 1.26.0 webového serveru a reverzní proxy nginx (Wikipedie). Nová verze přináší řadu novinek. Podrobný přehled v souboru CHANGES-1.26.

    Ladislav Hagara | Komentářů: 0
    dnes 04:33 | Nová verze

    Byla vydána nová verze 6.2 živé linuxové distribuce Tails (The Amnesic Incognito Live System), jež klade důraz na ochranu soukromí uživatelů a anonymitu. Přehled změn v příslušném seznamu. Tor Browser byl povýšen na verzi 13.0.14.

    Ladislav Hagara | Komentářů: 0
    dnes 04:22 | Nová verze

    Byla vydána nová verze 30.0.0 frameworku pro vývoj multiplatformních desktopových aplikací pomocí JavaScriptu, HTML a CSS Electron (Wikipedie, GitHub). Chromium bylo aktualizováno na verzi 124.0.6367.49, V8 na verzi 12.4 a Node.js na verzi 20.11.1. Electron byl původně vyvíjen pro editor Atom pod názvem Atom Shell. Dnes je na Electronu postavena celá řada dalších aplikací.

    Ladislav Hagara | Komentářů: 0
    dnes 04:11 | Nová verze

    Byla vydána nová verze 9.0.0 otevřeného emulátoru procesorů a virtualizačního nástroje QEMU (Wikipedie). Přispělo 220 vývojářů. Provedeno bylo více než 2 700 commitů. Přehled úprav a nových vlastností v seznamu změn.

    Ladislav Hagara | Komentářů: 0
    včera 23:22 | IT novinky

    Evropský parlament dnes přijal směrnici týkající se tzv. práva spotřebitele na opravu. Poslanci ji podpořili 584 hlasy (3 bylo proti a 14 se zdrželo hlasování). Směrnice ujasňuje povinnosti výrobců opravovat zboží a motivovat spotřebitele k tomu, aby si výrobky nechávali opravit a prodloužili tak jejich životnost.

    Ladislav Hagara | Komentářů: 2
    včera 16:11 | Nová verze

    Bylo oznámeno (cs) vydání Fedora Linuxu 40. Přehled novinek ve Fedora Workstation 40 a Fedora KDE 40 na stránkách Fedora Magazinu. Současně byl oznámen notebook Slimbook Fedora 2.

    Ladislav Hagara | Komentářů: 7
    včera 13:44 | Upozornění

    ČTK (Česká tisková kancelář) upozorňuje (X), že na jejím zpravodajském webu České noviny byly dnes dopoledne neznámým útočníkem umístěny dva smyšlené texty, které nepocházejí z její produkce. Jde o text s titulkem „BIS zabránila pokusu o atentát na nově zvoleného slovenského prezidenta Petra Pelligriniho“ a o údajné mimořádné prohlášení ministra Lipavského k témuž. Tyto dezinformace byly útočníky zveřejněny i s příslušnými notifikacemi v mobilní aplikaci Českých novin. ČTK ve svém zpravodajském servisu žádnou informaci v tomto znění nevydala.

    Ladislav Hagara | Komentářů: 17
    včera 13:33 | Komunita

    Byla založena nadace Open Home Foundation zastřešující více než 240 projektů, standardů, ovladačů a knihoven (Home Assistant, ESPHome, Zigpy, Piper, Improv Wi-Fi, Wyoming, …) pro otevřenou chytrou domácnost s důrazem na soukromí, možnost výběru a udržitelnost.

    Ladislav Hagara | Komentářů: 0
    včera 13:00 | Nová verze

    Společnost Meta otevírá svůj operační systém Meta Horizon OS pro headsety pro virtuální a rozšířenou realitu. Vedle Meta Quest se bude používat i v připravovaných headsetech od Asusu a Lenova.

    Ladislav Hagara | Komentářů: 0
    včera 04:33 | IT novinky

    Společnost Espressif (ESP8266, ESP32, …) získala většinový podíl ve společnosti M5Stack, čímž posiluje ekosystém AIoT.

    Ladislav Hagara | Komentářů: 0
    KDE Plasma 6
     (72%)
     (10%)
     (2%)
     (17%)
    Celkem 704 hlasů
     Komentářů: 4, poslední 6.4. 15:51
    Rozcestník

    Vývoj identity konektoru pro systém CzechIdM

    20.6.2011 12:17 | Přečteno: 1602× | Práce | poslední úprava: 21.6.2011 22:30

    Poslední dobou se moje pracovní povinnosti týkaly hlavně vývoje identity konektorů pro náš systém CzechIdM. Proto jsem se rozhodl, že napíši příspěvek, kde se pokusím letmo popsat problematiku Identity Managementu, Identity Connector Framework a vývoj konektorů v něm. Tento příspěvek zároveň volně navazuje na příspěvek mého kolegy Zdeňka Burdy o systému CzechIdM.

    Úkolem Identity Managementu (IdM) je správa celého životní cyklu identit v různých systémech od vzniku identity, změn parametrů až po zánik identity. Zjednodušeně řešeno se jedná o systém, který spravuje uživatelské identity (kdo nebo co má přístup) a jejich vztah k jednotlivým aplikacím a datům (kam má přístup) a na základě jaké pravomoci získal přístupy (schváleno nadřízeným nebo dle role, organizačního zařazení atd.). Tyto informace jsou jednoduše auditovatelné a reportovatelné v čase, a to vše v souladu s bezpečnostními politikami. Aby to nebylo tak jednoduché, tak pod identitou si lze představit uživatelský účet, certifikát, reprezentace PC v doméně, ale i role a organizační strukturu. Každý druh identit mívá vlastní životní cyklus.

    Výše popsané může vyvolávat dojem, že připojení koncových systémů k systému IdM musí vyžadovat jejich nemalé uzpůsobení pro podporu identity managementu. Opak je pravdou. Velkým přínosem IdM je snadnost připojení koncového systému bez nutnosti jeho přizpůsobení nebo dokonce i restartu. A jak na to? Využívají se obecnější přístupy mezi něž patří také tzv. identity konektory (Identity Connectors). A právě popisem vývoje jednoho takového konektoru v Identity Connector Frameworku pro náš systém CzechIdM se zabývá tento článek.

    Nejprve si povíme, co jsou to konektory a jak se používají. Poté se dostaneme k samotnému vývoji konektoru v Javě. Zde si popíšeme jednotlivé části konektoru a jejich vývoj. Závěrem si ukážeme, jak napsat jednoduchou klientskou aplikaci na ověření požadované funkcionality konektoru.

    Co to jsou identity konektory a k čemu slouží

    Identity konektory (dále jen konektory) jsou aplikační komponenty, prostřednictvím kterých se spravují uživatelské účty (a nemusí se jednat pouze o ně) na koncových systémech. Tvoří k nim jakousi fasádu, tj. poskytují jednotné rozhraní pro práci s nimi. Koncové systémy jsou v tomto případě všechny aplikace a systémy, které používají vlastní správu uživatelů, např. systémy pro sledování požadavků, HR systémy, mzdové systémy atd. Úkolem konektorů je poskytovat funkcionalitu pro připojení se k daným koncovým systémům a delegovat operace pro správu uživatelských účtů na tyto systémy.

    Konektory obecně poskytují metody pro:

    Toto jsou základní operace, které nám konektory poskytují pro práci s uživatelskými účty a skupinami. Obecně mohou konektory pracovat i s jinými objekty než jen s účty a skupinami, ale primárně jsou určeny a optimalizovány právě pro ně.

    Pokud chceme používat nějaký konektor v naší aplikaci, tak potřebujeme mít:

    1. Identity Connector Framework.
    2. Daný konektor samozřejmě (označován také jako bundle).

    Na oficiálních stránkách projektu Identity Connectors je k dispozici několik konektorů pro různé koncové systémy (od Google Apps až po LDAP). Framework lze získat z SVN-ka na adrese https://svn.java.net/svn/identityconnectors~svn (je potřeba se předem zaregistrovat). Po checkoutu máme k dispozici zdrojové kódy frameworku (jak pro Javu, tak i pro .NET) a zdrojové kódy několika konektorů. Právě zdrojové kódy konektorů jsou k nezaplacení při psaní vlastního konektoru, protože dokumentace není mnoho.

    Dále předpokládejme, že jsme provedli checkou trunku do adresáře ${connector_framework}. Potom zdrojové kódy konektorů jsou v adresáři ${connector_framework}/trunk/projects/bundles a framework pro Javu v adresáři ${connector_framework}/trunk/projects/framework/java. Zde jsou pouze zdrojové kódy, a proto je potřeba provézt build frameworku. Ten se provádí ANTem dle konfiguračního souboru ${connector_framework}/trunk/projects/framework/java/build.xml.

    Popis Identity Connector Frameworku

    Identity Connector Framework lze rozdělit na dvě části:
    1. Connector API - aplikace používají toto rozhraní pro volání implementovaných metod konektoru.
    2. Connector SPI - SPI deklaruje rozhraní, které implementují SPI vývojáři při tvorbě nových konektorů.

    Na obrázku níže je struktura Identity Connector Frameworku. Zde lze vidět, že prostřednictvím SPI komunikuje konektor s koncovými systémy a API poskytuje rozhraní pro volání metod konektoru.

    Connector API

    Connector API poskytuje jednotné rozhraní pro volání metod konektorů bez ohledu na to, jaké metody konektor implementuje. SPI vývojářům usnadňuje práci tím, že jsou zde již implementovány některé API funkcionality, které mohou jednoduše použít (s minimem vlastní implementace). Z pohledu klienta konektoru není většinou potřeba pro využívání těchto vlastností dělat vůbec nic, maximálně je nakonfigurovat.

    Mezi poskytované API vlastnosti patří například:

    Connector SPI

    Connector SPI tvoří sada několika rozhraní, které vývojáři konektorů musí implementovat, pokud požadují, aby konektor poskytoval danou operaci. Pro každou operaci existuje ve frameworku vlastní rozhraní.

    Seznam SPI operačních rozhraní:

    Další zajímavým SPI rozhraním je PoolableConnector. V tomto rozhraní je deklarována metoda checkAlive, která ověřuje, zda je daná instance konektoru stále aktivní. Pokud instance aktivní již není (např. vypršel timeout na spojení s koncovým systémem), tak by měla vyhodit výjimku RuntimeException, což dává frameworku signál, že je potřeba vytvořit novou instanci konektoru. Jelikož se tato metoda volá často, tak by měla být doba jejího běhu co možná nejkratší.

    Vývoj univerzálního SSH konektoru

    Nyní se již pustíme do vývoje vlastního konektoru. Bude se jednat se o "univerzální SSH konektor". Co se skrývá za tímto názvem? Popis SSH konektoru: Implementované třídy (popř. rozhraní) SSH konektoru:

    Třída SSHConfiguration

    Tato třída dědí od třídy AbstractConfiguration a zapouzdřuje konfiguraci konektoru. V tomto případě se v konfiguraci uvádějí údaje potřebné pro připojení se ke koncovému systému (uživatelské jméno, heslo, adresa koncového systému, port atd.), cesty k jednotlivým skriptům pro operace konektoru atd..Třída má defaultní konstruktor a pro každý konfigurační údaj jsou zde metody "set" a "get", přičemž metoda "get" je anotována anotací @ConfigurationProperty. Níže je uveden příklad pro uživatelské jméno klienta, pod kterým se konektor připojuje na koncový systém. Anotace @ConfigurationProperty má několik možných parametrů.
    @ConfigurationProperty(order = 3,
    	displayMessageKey = "SSH_USER_NAME",
    	helpMessageKey = "SSH_USER_HELP",
    	required = true)
    public String getUsername() {
    	return username;
    }
    
    Tato třída je také odpovědná za validaci vstupních hodnot. K tomuto účelu slouží metoda validate. Ta pouze kontroluje syntaktickou stránku konfiguračních údajů, neměla by ověřovat dostupnost zdrojů (např. připojení k databázi). Pokud zadané údaje nejsou "well-formed", tam metoda vyhazuje výjimku typu RuntimeException. Implementace rozhraní AbstractConfiguration je vyžadováno a musí ho implementovat všechny konektory.
    /**
     * Validuje konfiguraci konektoru. Kontroluje, zda jsou nastaveny všechny potřebné parametry.
     * Implementace by měla pouze kontrolovat syntaktickou stránku, tjn. jestli jsou vsechny potřebné
     * parametry "well-formed". Neměla by se snažit ověřovat dostupnost zdrojů, např. připojovat se k nim. 
     */
    @Override
    public void validate() {    	
    	if (StringUtil.isBlank(getHost())) {
    		throw new IllegalArgumentException("Hostname must be set.");
    	}    	
    	if (getPort() < 0 || getPort() >= 65535) {
    		throw new IllegalArgumentException("Port must be in range between 1 to 65535.");
    	}
    	if (StringUtil.isBlank(getUsername())) {
    		throw new IllegalArgumentException("Username must be specified.");
    	} 
    	if (!getEscapeMode().equals(SSHMessages.SSH_ESCAPE_MODE_DOUBLED) && !getEscapeMode().equals(SSHMessages.SSH_ESCAPE_MODE_BACKSLASH)) {
    		throw new IllegalArgumentException("Escape mode must be BACKSLASH or DOUBLED");
    	}    	
    }
    

    Třída SSHConnection

    Úkolem této třídy je spravovat spojení s koncovým systémem. Konstruktoru této třídy se předává instance konfigurační třídy SSHConfiguration. Pro připojení ke koncovému systému slouží metoda startConnection, pro ukončení spojení slouží metoda dispose a pro testování spojení metoda test.
    /**
     * Konstruktor třídy SSHConnection.
     * 
     * @param cfg konfigurace, tj. instance třídy SSHConfiguration
     * @throws Exception
     */
    public SSHConnection(SSHConfiguration cfg) throws Exception {
    	if (cfg == null) {
    		throw new Exception("Configuration not set.");
    	}    	
    	config = cfg;    	    	    	    
    } 
    
    /**
     * Metoda pro vytvoření spojení s koncovým systémem. Pokud je uveden privátní klíč, tak
     * se implicitně použije pro autentizaci. Jinak se použije dvojice uživatelské jméno a
     * heslo. Pokud je uveden otisk veřejného klíče serveru, ke kterému se připojujeme, tak
     * se použije pro jeho verifikaci.
     *  
     * @return Instance třídy Session. 
     */
    public Session startConnection() {		
    	String privateKey = asString(config.getPrivkey());    	    	    	
    	try {    		
    		if (!StringUtil.isBlank(privateKey)) {				
    			//Private key used for authentication
    			log.info("Private key used for authentication.");
    			createSSHConnectionWithPrivateKey(privateKey, config.getPrivkeyPassword());
    		} else {
    			//Authentication via password
    			log.info("Authentication via password.");
    			createSSHConnectionWithPassword(config.getPassword());
    		}
    	    	
    		session.connect(SSHConfiguration.CONNECTION_TIMEOUT);					
    		log.info("Succesfull connection.");
    	} catch (Exception ex){
    		log.error("Connecting to server failed. {0}",ex.getMessage());
    		throw new ConnectionFailedException("Connecting to server failed.");
    	} finally {
    		//clear user password
    		session.setPassword("");
    	}
    	return session;
    }
    
    /**
     * Metoda pro ukončení spojení.
     * {@inheritDoc}
     */
    public void dispose() {
    	log.info("Dispose connection.");    	
    	if (session != null) {
    		session.disconnect();
    	}
    }
    
    /**
     * Metoda testující navázané spojení.
     * {@inheritDoc}
     */
    public void test() {
    	config.validate();    	
    	startConnection();
    	dispose();
    }
    

    Třídy SSHUserFilterTranslator a SSHGroupFilterTranslator

    Tyto třídy dědí z AbstractFilterTranslator<String> a slouží pro vytváření vyhledávacích dotazů. Stačí implementovat pouze ty metody třídy AbstractFilterTranslator podle nichž chceme objekty vyhledávat. V našem případě stačilo implementovat metodu createEqualsExpression (viz. níže).
    /**
     * Metoda vytvoří dotaz (ve formě atributů pro příslušný skript koncového systému) 
     * pro vyhledání uživatelského účtu dle specifikovaného uživatelského jména (prozatím). 
     * Uživatelské jméno (instance třídy Name nebo Uid) je uloženo jako atribut v objektu 
     * filter.	
     */
    @Override
    protected String createEqualsExpression(EqualsFilter filter, boolean not) {		
    	if (not) {
    		throw new UnsupportedOperationException("Not supported yet.");
    	}
    	
    	String username = "";
    	Attribute attrib = filter.getAttribute();	
    	if (attrib == null) {
    		return null;
    	}
    	
    	if (attrib.is(Name.NAME)) {
    		username = ((Name)attrib).getNameValue();
    	} else if (attrib.is(Uid.NAME)) {
    		username = ((Uid)attrib).getUidValue();
    	} else if (attrib.getValue() != null) {
    		username = (String)attrib.getValue().get(0);
    	}		
    	
    	String operationName = SSHMessages.SSH_GETUSER;
    	String header = SSHMessages.SSH_HEADER_ACCOUNTID;         
                
            String scriptParams = String.format("%s\n%s\n%s\n", operationName, header, username);
    	return scriptParams;
    }
    
    Tato metoda vytvoří vyhledávací dotaz. Při vyhledávání konkrétního uživatele se vytvoří instance daného filtru (metodou createFilterTranslator třídy SSHConnector), vytvoří se odpovídající dotaz, který se poté předá jako parametr query metodě executeQuery (také ve třídě SSHConnector), která se již pokusí uživatelský záznam najít.
    /**
     * Metoda slouží pro spuštění dotazu nad objekty koncového systému.
     */
    public void executeQuery(ObjectClass oclass, String query, ResultsHandler handler, OperationOptions options) {    	    	
    	ConnectorObject object = null;
    	if (query == null) {
    		//Vylistovat vsechny objekty dane tridy.
    		Session session = connection.startConnection();
    		Iterator<Name> it = getAllObjectNames(oclass).iterator();
    		Name m = null;
    		String scriptParams;
    		try {
    			while (it.hasNext()) {
    				m = it.next();
    				scriptParams = createGetQuery(oclass, m.getNameValue());				
    				object = getConnectorObject(oclass, scriptParams, session);						
    				if (object != null) {
    					handler.handle(object);
    				}							
    			}
    		} catch (ConnectorException ex) {
    			throw new ConnectorException(ex.getMessage());
    		} finally {
    			if (session != null && session.isConnected()) {
    				session.disconnect();
    			}
    		}			
    	} else {
    		//Vylistovat pouze zaznam odpovidajici danemu dotazu (query).			
    		object = getConnectorObject(oclass, query, null);			
    		if (object != null) {
    			handler.handle(object);
    		}
    	}    	    	    	            
    }
    
    /**
     * Metoda navrací dle použité třídy objektů odpovídající filtr. 
     */
    public FilterTranslator<String> createFilterTranslator(ObjectClass oclass, OperationOptions options) {
    	if (oclass.is(ObjectClass.ACCOUNT_NAME)) {
    		return new SSHUserFilterTranslator();
    	} else if (oclass.is(ObjectClass.GROUP_NAME)) {
    		return new SSHGroupFilterTranslator();
    	}
    	return null;
    }
    

    Rozhraní SSHMessages

    Rozhraní SSHMessages slouží pouze pro definování použitých konstant v celém konektoru.

    Třída SSHConnector

    Je hlavní a nejpodstatnější třídou konektoru. Je anotována anotací @ConnectorClass a implementuje interface org.identityconnectors.framework.spi.Connector a SPI rozhraní operací, které má konektor podporovat. Jejími členskými proměnnými jsou instance tříd SSHConnection a SSHConfiguration. Vyžadován je bezparametrický konstruktor. Rozhraní org.identityconnectors.framework.spi.Connector deklaruje metody init(Configuration) a dispose, které jsou volány při každém spuštění operace nad koncovým systémem. Metoda init slouží pro získání konfigurace konektoru a pro navázání spojení, metoda dispose naopak spojení ukončuje. Spolu se třídou Configuration jsou to jediné třídy, které musejí být implementovány ve všech konektorech.
    package org.identityconnectors.ssh;
    
    import java.io.BufferedReader;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.nio.charset.Charset;
    import java.util.*;
    
    import org.identityconnectors.common.StringUtil;
    import org.identityconnectors.common.logging.Log;
    import org.identityconnectors.common.security.GuardedString;
    import org.identityconnectors.framework.common.exceptions.ConnectorException;
    import org.identityconnectors.framework.common.objects.*;
    import org.identityconnectors.framework.common.objects.AttributeInfo.Flags;
    import org.identityconnectors.framework.common.objects.filter.FilterTranslator;
    import org.identityconnectors.framework.spi.Configuration;
    import org.identityconnectors.framework.spi.Connector;
    import org.identityconnectors.framework.spi.ConnectorClass;
    import org.identityconnectors.framework.spi.operations.*;
    import org.identityconnectors.ssh.filters.SSHGroupFilterTranslator;
    import org.identityconnectors.ssh.filters.SSHUserFilterTranslator;
    
    import com.csvreader.CsvReader;
    import com.csvreader.CsvWriter;
    import com.jcraft.jsch.Channel;
    import com.jcraft.jsch.ChannelExec;
    import com.jcraft.jsch.Session;
    
    /**
     * Třída implementující funkcionalitu poskytovanou SSH konektorem.
     * 
     * @author Jaromír Mlejnek
     */
    @ConnectorClass(displayNameKey="SSH_Universal_Connector",
    		configurationClass = SSHConfiguration.class)
    public class SSHConnector implements Connector, CreateOp, DeleteOp, SearchOp<String>, 
    	UpdateOp, SchemaOp, TestOp {
    	
        private static Schema schema;        
        private static final String ENCODING = "UTF-8";
        
        private SSHConfiguration config;
        private SSHConnection connection;    
        
        private List<String> multiValueAttribs; 
    	
        //Logger
        Log log = Log.getLog(SSHConnector.class);
    	
        /**
         * Implicitní konstruktor.
         */
        public SSHConnector() {
        }    
    
       /**
        * Metoda navracející konfiguraci.
        */
        public Configuration getConfiguration() {
            return this.config;
        }
        
        /**
         * Metoda pro načtení konfigurace a inicializaci spojení. 
         */
        public void init(Configuration cfg) {    	
            config = (SSHConfiguration)cfg;
            try {
            	connection = new SSHConnection(config);        	
            } catch (Exception ex){
            	log.error("Exception during initialization.");
            	ex.printStackTrace();
            }
            
            if (config.getMultiValueAttributes() == null || config.getMultiValueAttributes().length == 0) {
            	multiValueAttribs = new ArrayList<String>();
            } else {
            	multiValueAttribs = Arrays.asList(config.getMultiValueAttributes());
            }          
        }
        
        /**
         * Metoda pro ukončení spojení.
         */
        public void dispose() {    	    
        	if (connection != null) {
        		connection.dispose();    		
        	}
        }
        
        /**
         * Metoda spouštějící test spojení. Pokud není spojení s koncovým systémem navázáno, tak 
         * metoda vyhodí výjimku.
         */
        public void test() {    	
        	log.info("SSHConnector - test");
        	connection.test();   	
        } 
        
        .
        .
        .
    
    Nemá cenu podrobně rozebírat implementaci všech metod, podívejme se tedy alespoň na metodu create. Jejími parametry jsou: Předpokládejme, že chceme vytvořit uživatelský účet (parametr ObjectClass oclas bude ObjectClass.ACCOUNT). Načteme si cestu k příslušnému skriptu pro vytvoření uživatele na koncovém systému (do proměnné pathToScript) a zavoláme metodu createOrUpdateUser. Ta projde předávanou množinu atributů a vytvoří vstupní parametry příslušného skriptu (ve formátu CSV). Pak spustí příslušný skript a předá mu dané CSV parametry. Skript vstup rozparsuje, vytvoří uživatelský účet a navrátí konektoru jedinečný identifikátor tohoto účtu (opět ve formátu CSV). Ten pak konektor deleguje volající aplikaci jako instanci třídy Uid (podtřída třídy Attribute, slouží jako jednoznačný identifikátor objektů).
    /**
     * Metoda pro zakládání objektu daného typu (ACCOUNT nebo GROUP) na koncovém systému.
     */
    public Uid create(ObjectClass oclass, Set<Attribute> attrs, OperationOptions options) {    	    	    	
    	String operationName = ""; 
    	String pathToScript = "";
            Uid returnUid = null;    	        
        
    	if (oclass.is(ObjectClass.ACCOUNT_NAME)) {
    		operationName = SSHMessages.SSH_CREATEUSER;   		
    		pathToScript = config.getCreateUser();    		
    		checkPathToScript(pathToScript, operationName);    		
    		returnUid = createOrUpdateUser(operationName, pathToScript, attrs);
    		
    	} else if (oclass.is(ObjectClass.GROUP_NAME)) {
    		operationName = SSHMessages.SSH_CREATEGROUP;
    		pathToScript = config.getCreateGroup();
    		checkPathToScript(pathToScript, operationName);
    		returnUid = createOrUpdateGroup(operationName, pathToScript, attrs);
    	}            
            return returnUid;     	
    }      
    
    /**
     * Metoda provádí dle názvu operace, cesty ke skriptu a zadaných atributů příslušnou operaci s 
     * uživatelským účtem.
     *  
     * @param operationName název prováděné operace.
     * @param pathToScript cesta k danému skriptu. 
     * @param attrs množina zadaných atributů. 
     * @return Uid uživatelského účtu, který se vytvořil nebo měnil.
     */
    private Uid createOrUpdateUser(String operationName, String pathToScript, Set<Attribute> attrs) {
    	StringBuffer userHeader = new StringBuffer();
            List<String> dataForUserLine = new ArrayList<String>();
               
    	Attribute attrib = null;
    	Iterator<Attribute> it = attrs.iterator();
    	while (it.hasNext()) {
    		attrib = it.next();
    		if (attrib.is(Name.NAME)) {							
    			String name = getName(attrib);
    			userHeader.append(SSHMessages.SSH_HEADER_ACCOUNTID);
    			userHeader.append(SSHConfiguration.DELIMITER);
    			dataForUserLine.add(name);								
    		} else if (attrib.is(OperationalAttributes.PASSWORD_NAME)) {					        
    	                userHeader.append(SSHMessages.SSH_HEADER_PASSWORD);
    	                userHeader.append(SSHConfiguration.DELIMITER);
    	                dataForUserLine.add(getPassword(attrib));		        
    		} else {
    			userHeader.append(attrib.getName());
        		        userHeader.append(SSHConfiguration.DELIMITER);
        		        dataForUserLine.add(getAttributeValue(attrib));
    		}    			
    	}
    	//Odstranime delimiter na konci radku
    	userHeader = removeLastChar(userHeader);
    	
    	String scriptParams = createCommandCSV(operationName, userHeader, dataForUserLine);			
    	String result = runCommand(pathToScript, scriptParams);		
    	String accountUid = "";
    	try {
    		CsvReader reader = CsvReader.parse(result);
    		reader.setDelimiter(SSHConfiguration.DELIMITER);
    		reader.setEscapeMode(getCsvReaderMode());
    		reader.readHeaders();
    		reader.readRecord();
    		accountUid = reader.get(0);
    	} catch (IOException ioExc) {
    		log.error("Exception during read from CSV file. \nError: {0}", ioExc.getMessage());
    	} 	
    	if (StringUtil.isBlank(accountUid)) {
    		return null;
    	}
    	return new Uid(accountUid);
    }
    
    Na podobném principu pracují i ostatní operace (metody update, delete atd.). Vždy se určí cesta k odpovídajícímu skriptu, sestaví se jeho vstupní parametry ve formátu CSV, které se mu následně předají, skript se provede a případný výstup je předán konektoru (opět jako CSV). Konektor výstup rozparsuje a dále dle potřeby zpracuje. Posledním krokem, co musíme při vývoji konektoru udělat, je napsat ANT build skript build.xml, podle kterého se provede build konektoru. Není to nic těžkého, protože zde stačí pouze uvézt cestu ke konfiguračnímu souboru connector_build.xml (standardně se nachází v adresáří ${connector_framework}/trunk/projects/framework/java). V tomto souboru jsou uvedeny všechny potřebné cesty k třídám frameworku atd. Pokud tedy neproběhne build konektoru bez problému, tak je nejspíše chybně nastavená některá z cest v tomto souboru. V našem případě obsahuje soubor build.xml následující řádky.
    <project name="connector-ssh" default="all">
        <property name="framework.dir" value="../java"/>
        <import file="${framework.dir}/connector_build.xml"/>
    </project>
    
    Spolu s build skriptem je ještě potřeba vytvořit build.properties soubor, ve kterém je uvedena použitá verze franeworku a název konektoru. V našem případě:
    MAJOR=1
    MINOR=0
    ConnectorBundle-FrameworkVersion=1.0
    ConnectorBundle-Name=org.identityconnectors.ssh
    connectorName=org.identityconnectors.ssh.SSHConnector
    
    Nyní již můžeme provézt buidl konektoru ANTem.

    Vzorový klient konektoru

    Nyní, když již máme vytvořený náš SSH konektor, tak se podíváme, jakým způsobem ho můžeme použít. Ukážeme si, jak napsat jednoduchou aplikaci, která bude tento konektor používat pro správu uživatelů na systému Request Tracker.

    Vytvoříme si obyčejný Java projekt, přidáme do něho knihovny connector-framework.jar a connector-framework-internal.jar a můžeme začít. Vytvoříme hlavní metodu main, do které zapíšeme následující kód.

    try {
    	// 1. Soubor s konektorem.
    	File bundleDirectory = new File("/home/jarda/workspace/Connector_Client/bundles");
    	URL db1Url = IOUtil.makeURL(bundleDirectory, "org.identityconnectors.ssh-1.0.956.jar");			
    	
    	// 2. Inicializace connector manageru, který udržuje seznam konektorů (přesněji informace o nich).
    	ConnectorInfoManagerFactory factory = ConnectorInfoManagerFactory.getInstance();
    	ConnectorInfoManager manager = factory.getLocalManager(db1Url);
    	
    	List<ConnectorInfo> connectorInfos = manager.getConnectorInfos();
    	
    	// 3. Vypsání dostupných konektorů.
    	System.out.println("Available connector bundles:");
    	for (ConnectorInfo connectorInfo : connectorInfos) {
    		ConnectorKey key = connectorInfo.getConnectorKey();
    		System.out.println(key.toString());
    	}
    	
    	// 4. Identifikátor určitého konektoru.
    	ConnectorKey ffKey = new ConnectorKey("org.identityconnectors.ssh",
    	"1.0.956",
    	"org.identityconnectors.ssh.SSHConnector");			
    	 ConnectorInfo ffConInfo = manager.findConnectorInfo(ffKey);
    	 
    	 // 5. Načítá defaultní konfiguraci konektoru.
    	 APIConfiguration ffConfig = ffConInfo.createDefaultAPIConfiguration();
    	
    	 // 6. Vypsání názvů konfiguračních údajů. 
    	 ConfigurationProperties ffConfigProps = ffConfig.getConfigurationProperties();
    	 for (String name : ffConfigProps.getPropertyNames()) {
    		 System.out.println("property: " + name);
    	 }	
    	 			 
    	 // 7. Vypsání názvů konektorem podporovaných operací.
    	 for (Class<? extends APIOperation> supportedOp : ffConfig.getSupportedOperations()) {
    		 System.out.println("supported operation: " + supportedOp.getSimpleName());
    	 }
    	 
    	 // 8. Konfigurace konektoru.
    	 ffConfigProps.setPropertyValue("host", "localhost");
    	 ffConfigProps.setPropertyValue("port", 22);
    	 ffConfigProps.setPropertyValue("username", "test");
    	 ffConfigProps.setPropertyValue("password", new GuardedString("demo1234".toCharArray()));			 			 			 
    	 
    	 ffConfigProps.setPropertyValue("user", "/home/jarda/workspace/bcv-idm/Realizace/ssh_connector/skripty_RT/sshuni_rt.sh");
    	 ffConfigProps.setPropertyValue("createUser", "/home/jarda/workspace/bcv-idm/Realizace/ssh_connector/skripty_RT/sshuni_rt.sh");
    	 ffConfigProps.setPropertyValue("deleteUser", "/home/jarda/workspace/bcv-idm/Realizace/ssh_connector/skripty_RT/sshuni_rt.sh");
    	 ffConfigProps.setPropertyValue("enableUser", "/home/jarda/workspace/bcv-idm/Realizace/ssh_connector/skripty_RT/sshuni_rt.sh");
    	 ffConfigProps.setPropertyValue("disableUser", "/home/jarda/workspace/bcv-idm/Realizace/ssh_connector/skripty_RT/sshuni_rt.sh");
    	 ffConfigProps.setPropertyValue("updateUser", "/home/jarda/workspace/bcv-idm/Realizace/ssh_connector/skripty_RT/sshuni_rt.sh");			 			 
    	
    	 ffConfigProps.setPropertyValue("listObjects", "/home/jarda/workspace/bcv-idm/Realizace/ssh_connector/skripty_RT/sshuni_rt.sh");
    	 
    	 ffConfigProps.setPropertyValue("escapeMode", "DOUBLED");
    	 ffConfigProps.setPropertyValue("multiValueAttributes", new String[] {});			 
    	 ffConfigProps.setPropertyValue("multiValueAttributesSeparator", ',');			 
    	 
    	 // 9. Vytvoření instance konektoru, validace konfiguračních údajů, test spojení.
    	 ConnectorFacade ffConnector = ConnectorFacadeFactory.getInstance().newInstance(ffConfig);				           
    	 ffConnector.validate();
    	 ffConnector.test();
    	 
    	 //Nyní již můžeme nad konektorem volat implementované metody.
    	 
    	 //Vytvoření nového uživatelského účtu "sokrates11".
    	 Set<Attribute> attrs = new HashSet<Attribute>();
    	 attrs.add(AttributeBuilder.build("Name", "sokrates1"));
    	 attrs.add(AttributeBuilder.build("EmailAddress", "sokrates1@example.com"));
    	 attrs.add(AttributeBuilder.build("Gecos", "sokr1"));
    	 Uid uid = ffConnector.create(ObjectClass.ACCOUNT, attrs, null);
    	 System.out.println("New users's UID: " + uid.getUidValue());
    	 
    	 //Vyhledání uživatele "sokrates11".
    	 ffConnector.getObject(ObjectClass.ACCOUNT, uid, null);
    	 
    	 //Smazání uživatele "sokrates11".
             ffConnector.delete(ObjectClass.ACCOUNT, uid, null);			 
    		 
    } catch (Exception ex) {
    	ex.printStackTrace();
    }									
    
    V prvním kroku nastavíme cestu k danému konektoru. Dále si vytvoříme instanci třídy ConnectorInfoManager, která udržuje seznam načtených konektorů. Ve třetím kroku si všechny načtené konektory vypíšeme. Poté si ve čtvrtém kroku vytvoříme referenci na konektor a v pátém načteme defaultní API konfiguraci. V šestém kroku vypíšeme názvy všech konfiguračních údajů a v sedmém všechny podporované operace na koncovém systému. V kroku číslo 8 nastavíme požadované konfigurační údaje a v devátém kroku již vytvoříme instanci konektoru. Nyní již můžeme volat požadované metody. Zkusíme si tedy vytvořit nový uživatelský účet na systému Request Tracker, pak ho vyhledáme a na závěr smažeme. Výstup celého běhu klientské aplikace je níže.
    Available connector bundles:
    ConnectorKey( bundleName=org.identityconnectors.ssh bundleVersion=1.0.956 connectorName=org.identityconnectors.ssh.SSHConnector )
    property: host
    property: port
    property: username
    property: password
    property: privkey
    property: privkeyPassword
    property: hostkey
    property: user
    property: createUser
    property: deleteUser
    property: enableUser
    property: disableUser
    property: updateUser
    property: group
    property: createGroup
    property: deleteGroup
    property: updateGroup
    property: listObjects
    property: escapeMode
    property: multiValueAttributes
    property: multiValueAttributesSeparator
    supported operation: GetApiOp
    supported operation: SearchApiOp
    supported operation: ScriptOnConnectorApiOp
    supported operation: SchemaApiOp
    supported operation: DeleteApiOp
    supported operation: ValidateApiOp
    supported operation: TestApiOp
    supported operation: UpdateApiOp
    supported operation: CreateApiOp
    Thread Id: 1	Time: 2011-06-05 16:30:03.223	Class: org.identityconnectors.framework.api.operations.ValidateApiOp	Method: validate	Level: OK	Message: Enter: validate()
    Thread Id: 1	Time: 2011-06-05 16:30:03.229	Class: org.identityconnectors.framework.api.operations.ValidateApiOp	Method: validate	Level: OK	Message: Return: null
    Thread Id: 1	Time: 2011-06-05 16:30:03.240	Class: org.identityconnectors.framework.api.operations.TestApiOp	Method: test	Level: OK	Message: Enter: test()
    Thread Id: 1	Time: 2011-06-05 16:30:03.267	Class: org.identityconnectors.ssh.SSHConnector	Method: test	Level: INFO	Message: SSHConnector - test
    Thread Id: 1	Time: 2011-06-05 16:30:03.267	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Authentication via password.
    Thread Id: 1	Time: 2011-06-05 16:30:03.574	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Succesfull connection.
    Thread Id: 1	Time: 2011-06-05 16:30:03.575	Class: org.identityconnectors.ssh.SSHConnection	Method: dispose	Level: INFO	Message: Dispose connection.
    Thread Id: 1	Time: 2011-06-05 16:30:03.587	Class: org.identityconnectors.framework.api.operations.TestApiOp	Method: test	Level: OK	Message: Return: null
    Thread Id: 1	Time: 2011-06-05 16:30:03.605	Class: org.identityconnectors.framework.api.operations.CreateApiOp	Method: create	Level: OK	Message: Enter: create(ObjectClass: __ACCOUNT__, [Attribute: {Name=EmailAddress, Value=[sokrates1@example.com]}, Attribute: {Name=Gecos, Value=[sokr1]}, Attribute: {Name=Name, Value=[sokrates1]}], null)
    Thread Id: 1	Time: 2011-06-05 16:30:03.618	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Authentication via password.
    Thread Id: 1	Time: 2011-06-05 16:30:03.754	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Succesfull connection.
    Thread Id: 1	Time: 2011-06-05 16:30:04.306	Class: org.identityconnectors.ssh.SSHConnection	Method: dispose	Level: INFO	Message: Dispose connection.
    Thread Id: 1	Time: 2011-06-05 16:30:04.312	Class: org.identityconnectors.ssh.SSHConnection	Method: dispose	Level: INFO	Message: Dispose connection.
    Thread Id: 1	Time: 2011-06-05 16:30:04.317	Class: org.identityconnectors.framework.api.operations.CreateApiOp	Method: create	Level: OK	Message: Return: Attribute: {Name=__UID__, Value=[user/143]}
    New users's UID: user/143
    Thread Id: 1	Time: 2011-06-05 16:30:04.322	Class: org.identityconnectors.framework.api.operations.GetApiOp	Method: getObject	Level: OK	Message: Enter: getObject(ObjectClass: __ACCOUNT__, Attribute: {Name=__UID__, Value=[user/143]}, null)
    Thread Id: 1	Time: 2011-06-05 16:30:04.335	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Authentication via password.
    Thread Id: 1	Time: 2011-06-05 16:30:04.439	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Succesfull connection.
    Thread Id: 1	Time: 2011-06-05 16:30:04.838	Class: org.identityconnectors.ssh.SSHConnection	Method: dispose	Level: INFO	Message: Dispose connection.
    Thread Id: 1	Time: 2011-06-05 16:30:04.858	Class: org.identityconnectors.ssh.SSHConnection	Method: dispose	Level: INFO	Message: Dispose connection.
    Thread Id: 1	Time: 2011-06-05 16:30:04.859	Class: org.identityconnectors.framework.api.operations.GetApiOp	Method: getObject	Level: OK	Message: Return: {ObjectClass=ObjectClass: __ACCOUNT__, Attributes=[Attribute: {Name=Status, Value=[UNLOCK]}, Attribute: {Name=password, Value=[]}, Attribute: {Name=__UID__, Value=[user/143]}, Attribute: {Name=EmailAddress, Value=[sokrates1@example.com]}, Attribute: {Name=__NAME__, Value=[user/143]}, Attribute: {Name=Name, Value=[sokrates1]}], Name=Attribute: {Name=__NAME__, Value=[user/143]}, Uid=Attribute: {Name=__UID__, Value=[user/143]}}
    Thread Id: 1	Time: 2011-06-05 16:30:04.862	Class: org.identityconnectors.framework.api.operations.DeleteApiOp	Method: delete	Level: OK	Message: Enter: delete(ObjectClass: __ACCOUNT__, Attribute: {Name=__UID__, Value=[user/143]}, null)
    Thread Id: 1	Time: 2011-06-05 16:30:04.868	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Authentication via password.
    Thread Id: 1	Time: 2011-06-05 16:30:05.040	Class: org.identityconnectors.ssh.SSHConnection	Method: startConnection	Level: INFO	Message: Succesfull connection.
    Thread Id: 1	Time: 2011-06-05 16:30:05.132	Class: org.identityconnectors.ssh.SSHConnection	Method: dispose	Level: INFO	Message: Dispose connection.
    Thread Id: 1	Time: 2011-06-05 16:30:05.134	Class: org.identityconnectors.ssh.SSHConnection	Method: dispose	Level: INFO	Message: Dispose connection.
    Thread Id: 1	Time: 2011-06-05 16:30:05.134	Class: org.identityconnectors.framework.api.operations.DeleteApiOp	Method: delete	Level: OK	Message: Return: null
    

    Závěrem

    V tomto článku jsme si ukázali, co to jsou identity konektory, k čemu se používají v identity managementu. Zběžně jsme si popsali vývoj jednoho takového konektoru, který používáme v našem Identity Manageru CzechIdM. Jak již bylo uvedeno, tak oficiální dokumentace týkající se connector frameworku není mnoho. Případným zájemcům o bližší informace lze doporučit hlavně zdrojové kódy konektorů distribuovaných spolu s frameworkem a JavaDOC jednotlivých tříd. A snad i tento článek může sloužit jako takový letmý úvod do problematiky identity konektorů.

    V případě dotazů mne nebo kolegy neváhete kontaktovat na mailu info@bcvsolutions.eu.

           

    Hodnocení: 58 %

            špatnédobré        

    Obrázky

    Vývoj identity konektoru pro systém CzechIdM, obrázek 1

    Tiskni Sdílej: Linkuj Jaggni to Vybrali.sme.sk Google Del.icio.us Facebook

    Komentáře

    Vložit další komentář

    20.6.2011 16:03 kavol | skóre: 28
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Asimov by se divil ... nejen roboti maj identitu, už i pouhé konektory :-)
    20.6.2011 20:41 moris | blog: Morisův Blog
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    uznávám, že slovní spojení "identity konektory" je poněkud zavádějící :-)
    pavlix avatar 21.6.2011 18:16 pavlix | skóre: 54 | blog: pavlix
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Zajímalo by mě, jestli je tenhle blogpost něco víc než jen reklama na proprietární software v open source blogu.

    Franta už se ptal u toho minulého článku na licenci a NEDOSTAL odpověď. Byl bych docela nerad, aby se nám tu něco podobného rozmáhalo.

    Já už tu vlastně ani nejsem. Abclinuxu umřelo.
    21.6.2011 18:23 pasmen | skóre: 45 | blog: glob | Praha
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Tady je někdo magor.
    21.6.2011 20:27 Zdeněk Burda | skóre: 61 | blog: Zdendův blog | Praha
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Chceš určovat co kdo může psát do blogu?

    Jestli je Jardův zápisek reklama nebo ne nebudu hodnotit. Do toho kde si svůj text zveřejní mu nekecám a to že píše o své práci snad není nic špatného. Kdyby spravoval unixové servery, bude asi psát o nich... Není náhodou reklama to co si cpeš do patičky?

    V mém textu jsem hned na začátku psal "CzechIdM je open source Identity Manager šířený zdarma pod naší vlastní licencí." Nejsem si vědom toho, že bysme museli licenci publikovat veřejně. Až spolu budeme řešit služby ve vztahu zákazník-dodavatel, rád s tebou licenční podmínky proberu. Věř, že jsme se vhodnou licencí zabývali velmi intenzivně.

    -- Nezdar není hanbou, hanbou je strach z pokusu.
    pavlix avatar 21.6.2011 21:09 pavlix | skóre: 54 | blog: pavlix
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Chceš určovat co kdo může psát do blogu?
    On můj dotaz nějak určuje, kdo co může do blogu psát?

    Pokud chceš konzultovat, co je ok a co není, budeš se muset obrátit na provozovatele serveru :). Pravděpodobně tě nechá do blogu psát relativně cokoli, jen se rozhodne, jestli tomu blogu bude dělat reklamu (zobrazovat ho na seznamu nových blogpostů) nebo ne.
    V mém textu jsem hned na začátku psal "CzechIdM je open source Identity Manager šířený zdarma pod naší vlastní licencí." Nejsem si vědom toho, že bysme museli licenci publikovat veřejně.
    Franta se slušně zeptal pod minulým blogpostem a mně přišlo na místě, aby dostal alespoň slušnou odpověď. Nedostal žádnou (tedy ani informaci, že licence je tajná).

    Jinak pokud jsou licenční podmínky tak tajné, tak je pro mě tvrzení, že se jedná o open source (alespoň v tom smyslu, jak ho definuje OSI) neověřená informace, když ani nevím, jestli autor toho tvrzení tomuto pojmu vůbec rozumí.
    Až spolu budeme řešit služby ve vztahu zákazník-dodavatel, rád s tebou licenční podmínky proberu.
    Břemeno získání důvěry bývá obvykle na straně dodavatele :).
    Já už tu vlastně ani nejsem. Abclinuxu umřelo.
    21.6.2011 22:27 moris | blog: Morisův Blog
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Hlavním smyslem tohoto příspěvku je:
    1. Zjednodušeně popsat problematiku identity managementu.
    2. Ukázat, jakým způsobem lze připojit koncový systém k identity manageru.
    3. Zběžně popsat Identity Connector framework.
    4. Zběžně popsat vývoj konektoru pod daným frameworkem.
    Nevidím důvod, proč při popisu vývoje konektoru nemůžu konstatovat, že je používán tím a tím systémem. Nechávám na každém, ať zhodnotí, zda převládá reklama, a nebo obecný popis dané problematiky.
    pavlix avatar 21.6.2011 22:37 pavlix | skóre: 54 | blog: pavlix
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Já bohužel četl už ten předchozí.
    Já už tu vlastně ani nejsem. Abclinuxu umřelo.
    22.6.2011 09:02 Lukáš Cirkva | skóre: 10 | Praha
    Rozbalit Rozbalit vše Re: Vývoj identity konektoru pro systém CzechIdM
    Příspěvek do blogu je o technologii Identity Connectors frameworku. Popisuje co to jsou identity konektory a k čemu slouží a obsahuje podrobnou ukázku praktické realizace SSH konektoru. Text je ryze technického rázu od vývojáře javy o vývoji a použití technologie, která je opensource (Oracle se jí snaží uzavřít). Tento příspěvek reaguje na četné dotazy, které jsme za poslední měsic obdrželi, například dotaz v blogu.

    CzechIdM je SW s otevřeným kódem, do kterého může klient zasahovat. Informace o licenci jsou na webu CzechIdM nebo zde.

    Kolegové začali psát o CzechIdM, protože si myslíme, že téma identity managementu a opensource řešení vyvíjené v ČR může čtenáře zajímat. Proč si to myslíme?
    • Máme dotazy na toto téma i z komunity. (Protože identity management děláme opravdu hodně dlouho i na jiných komerčních produktech, školíme a publikujeme...)
    • Na předchozí články o této tématice byla dobrá odezva v diskuzi a 100% hodnocení čtenáři viz: Správa uživatelů v síti a viz: Správa uživatelů v síti II
    Zda sem psát o tématu identity managementu a CzechIdM, ať zhodnotí (a hodnotí u každého příspěvku) sami čtenáři. Zatím jsou u každého příspěvku kladné body 86% a 67% pro.

    Založit nové vláknoNahoru

    ISSN 1214-1267   www.czech-server.cz
    © 1999-2015 Nitemedia s. r. o. Všechna práva vyhrazena.