<<if: ZXIDBOOK>> <<else: >>ZXID ID-WSF API (SOAP Web Services) ############### <<author: Sampo Kellomäki (sampo@iki.fi)>> <<cvsid: $Id: zxid-wsf.pd,v 1.4 2010-01-08 02:10:09 sampo Exp $>> <<class: article!a4paper!!ZXID-WSF 01>> <<define: ZXDOC=ZXID Id-WSF API>> <<abstract: ZXID.org Identity Management toolkit implements standalone SAML 2.0 and Liberty ID-WSF 2.0 stacks. This document describes the API for making ID-WSF identity web services calls, i.e. SOAP calls. Discovery and providing a service are also covered. >> <<maketoc: 1>> 1 Introduction ============== Here we describe the general philosophy of the ZXID ID-WSF APIs. Some function level documentation is available from <<link:../ref/html/index.html: Function reference>>. 1.1 Other documents ------------------- <> <<htmlpreamble:

# ZXID ID-WSF API (SOAP Web Services)

>> 13 ID-WSF Features of ZXID ========================== <<fi: >> 13.1 EPR Cache -------------- Calling an ID Web Service requires an Endpoint Reference (EPR) that indicates not only the URL of the web service, but also the security mechanism and any tokens or credentials required by the security mechanism, collectively called the +metadata+ of the EPR.<<footnote: While the role of the EPR metadata is broadly similar to SAML metadata, the two should not be confused.>> An EPR can be obtained from two sources 1. From the SSO assertion containing an attribute statement that has the EPR. This is called the +bootstrap+ method. 2. By calling the discovery service. Either way, the EPRs are cached in the SSO session as files with paths like /var/zxid/ses/SESID/SVCTYPE,SHA1 where +SESID+ is the SSO session ID (safe base64 of a random number). +SVCTYPE+ and +SHA1+ form a unique file name inside the session directory. +SVCTYPE+ component allows easy identification of the EPRs that are relevant for calling a given service. The +SHA1+ component is a SHA1 hash over the canonical XML representation of the EPR. When bootstrap EPRs are present in the SSO assertion, they are automatically extracted to the EPR cache (internally zxid_snarf_eprs_from_ses() is called). Generally this will yield at least bootstrap EPR for discovery service. Later, when calling web services, discovery is automatically performed as needed and the results are automatically populated to the EPR cache (internally zxid_cache_epr() is called). When calling higher level WSC APIs, the discovery will happen automatically, but if you work in terms of lower level APIs, you can obtain an EPR by calling zxid_get_epr() which will first consult the cache, and if there is a miss, then try discovering the EPR using discovery service EPR, if any (usually one was obtained as bootstrap during the SSO). 13.2 High level WSC API ----------------------- The high level API is based on the idea of constructing the SOAP body (the payload) as a string and passing that to function that performs the SOAP call and returns response. 01 cf = zxid_new_conf_to_cf(CONF); 02 03 res = zxid_simple_cf(cf, cl, qs2, 0, 0x1fff); 04 switch (res[0]) { 05 default: 06 ERR("Unknown zxid_simple() response(%s)", res); 07 case 'd': break; /* Logged in case */ 08 } 09 sid = strstr(res, "sesid: "); 10 zxid_get_ses(cf, &sess, sid); 11 env = zxid_callf(cf, &sess, 0,0,0, zx_xmlns_idhrxml, 12 "" 13 "" 14 "%s" 15 "%s" 16 "" 17 "", cgi.select, cgi.data); 18 ZXID_CHK_STATUS(env, idhrxml_ModifyResponse, 19 hrxml_resp = "Modify failed"; break); 20 hrxml_resp = "Modify OK"; On lines 1-10 a single sign on is done to prepare the session and EPR cache. 13.2.1 zxid_callf() - Make SOAP call with specified body ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ env = zxid_callf(cf, ses, svctype, url, di_opt, az_cred, fmt, ...) zxid_callf() first creates the body of the SOAP call by expanding the format string (which may involve additional arguments). Next it parses the string into XML data structure. If the format string uses unresolved namespaces, they are resolved using the svctype argument. Next zxid_callf() will attempt to locate an EPR for the service type. This may already be in cache, or discovery step may be performed. Then zxid_callf() augments the XML data structure with Liberty ID-WSF mandated headers. It will look at the security mechanism and token specified in the EPR and perform appropriate steps to create WS-Security header and apply signature as needed. Finally a SOAP call is made. The result is XML parsed and returned as SOAP envelope object. cf:: Configuration object, see zxid_new_conf_to_cf() ses:: Session object, used to locate EPRs, see zxid_get_ses() svctype:: Service type and namespace that is applicable to the body. This is one of the constants from c/zx-ns.h url:: (Optional) If provided, this argument has to match either the ProviderID, EntityID, or actual service endpoint URL. di_opt:: (Optional) Additional discovery options for selecting the service, query string format az_cred:: (Optional) Additional authorization credentials or attributes, query string format. These credentials will be populated to the attribute pool in addition to the ones obtained from SSO and other sources. Then a PDP is called to get an authorization decision (as well as obligations we pledge to support). See also PEPMAP configuration option. This implementes generalized (application independent) Requestor Out and Requestor In PEPs. To implement application dependent PEP features you should call zxid_az() directly. fmt:: printf style format string that is used to describe the body of the call as a string. If fmt contains format specifiers, then additional arguments are used to expand these. Return value:: SOAP envelope as a string. > N.B. Although the request body is formulated as a string, > you can only use XML constructs whose schema is known to > ZXID. The resulting XML must be validly parseable by ZXID. 13.2.2 ZXID_CHK_STATUS() - Macro for checking OK status ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ZXID_CHK_STATUS(env, resp_tag, abort-action); ZXID_CHK_STATUS(env, idhrxml_ModifyResponse, break); In ID-WSF web services it is very common that the overall outcome of the operation is expressed by <Status> element at top level. This macro makes it easy to check the status. If status is NOT "OK", then +abort-action+ is invoked. This would be whatever needed in your program to report error and abort further processing of the response. It could be a ~break~, ~return~, or even a ~goto~ statement. env:: SOAP envelope representing the response resp_tag:: The response tag as it appears as a child of env->Body abort-action:: Statement, or statements, used to report error and abort processing. There is no return value. This macro is "procedural". 13.3 Low Level WSC API ---------------------- Typical code for calling a web service at low level 01 #include <zx/errmac.h> 02 #include <zx/zxid.h> 03 #include <zx/zxidconf.h> 04 #include <zx/saml2.h> 05 #include <zx/wsf.h> 06 #include <zx/c/zx-ns.h> 07 08 struct zx_e_Envelope_s* env; 09 struct zx_a_EndpointReference_s* epr; 10 epr = zxid_get_epr(cf, ses, zx_xmlns_dap, 1); 11 if (epr) { 12 env = zx_NEW_e_Envelope(cf->ctx); 13 env->Header = zx_NEW_e_Header(cf->ctx); 14 env->Body = zx_NEW_e_Body(cf->ctx); 15 env->Body->Query = zxid_mk_dap_query(cf, ...); /* See ID-DAP inteface */ 16 env = zxid_wsc_call(cf, ses, epr, env); /* The beef */ 17 if (env->dap_QueryResponse) 18 D("Result is LDIF(%.*s)", 19 env->Body->dap_QueryResponse->Data->LDIF->gg.content->len, 20 env->Body->dap_QueryResponse->Data->LDIF->gg.content->s); 21 } 1. On line 10 zxid_get_epr() is used with following arguments cf:: Configuration object ses:: Session object (generallly obtained from SSO) zx_xmlns_dap:: The +service type+ of the service that is to be called. Generally this is same as the namespace URI. The include <zx/c/zx-ns.h> contains macros, such as ~zx_xmlns_dap~, for the namespaces supported by zxid. Alternatively you could simply supply the string. 1:: The last argument ("1", one) is an iterator index that, in case of multiple EPRs allows you to pick which one to use. Most common usage is to simply supply 1 which picks the first one.<<footnote: However, the ordering of the eprs in not currently well defined and may change from one version of ZXID to another.>> 13.3.1 zxid_get_epr() - Obtain EPR fron cache or by discovery ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ epr = zxid_get_epr(cf, ses, svctype, n); See if an EPR for service +svctype+ is available in current session EPR cache. If not, see if discovery EPR is available and try to discover the EPR for the +svctype+. Note that the discovery is transparent to the programmer - indeed programmer can not know whether EPR was served from cache (where it may have appeared by virtue of a bootstrap) or whether it was discovered. cf:: Configuration object, see zxid_new_conf_to_cf() ses:: Session object, used to locate EPRs in cache, see zxid_get_ses() svctype:: Service type (usually namespace of the service). This is one of the constants from c/zx-ns.h n:: Ordinal. If more than one EPR is available, specifies which one is desired. Usually 1 is supplied, to pick the first EPR. Return Value:: An EPR object (URL of the service, security mechanism, and possibly security token (SAML assertion)). 13.4 ID-DAP Interface --------------------- The ID-DAP search filters and data model are very similar to LDAP, see [RFC2251] for further explanation. 13.4.1 Short example of using low level API ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ env->Body->dap_Query = zxid_mk_dap_query(cf, 0, /* No tests */ zxid_mk_dap_query_item(cf, zxid_mk_dap_select(cf, 0, /* DN from ID-WSF */ "objecttype=svcprofile", 0, /* all attributes */ 1, /* chase symlinks */ ZXID_DAP_SCOPE_SUBTREE, 0, /* no size limit */ 0, /* no time limit */ 0), /* return data */ 0, /* regular data entries */ 0, /* No predefined operation */ 0, /* No sorting. */ 0, /* No changed since specification. */ 0, /* Do not include LDAP common attributes. */ 0, /* Start from first result (offset == 0) */ 0, /* Return all results (count == 0) */ 0, /* Do not request snapshot */ 0, /* Do not refer to snapshot */ 0), /* No contingent item ID reference */ 0); /* No subscriptions */ As can be seen, it is common to specify nearly all arguments as 0, relying on default values. Thus the code typically appears without comments as env->Body->dap_Query = zxid_mk_dap_query(cf, 0, zxid_mk_dap_query_item(cf, zxid_mk_dap_select(cf, 0, "objecttype=svcprofile", 0, 1, ZXID_DAP_SCOPE_SUBTREE, 0, 0, 0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), /* 10 zeroes here */ 0); /* No subscriptions */ 13.4.2 Fully winded example of using low level API ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As a query can also have test and subscription clauses, the fully winded example becomes quite onerous. Of course for many daily tasks you will not need all the frills, or you can just use the simple API instead. env->Body->dap_Query = zxid_mk_dap_query(cf, zxid_mk_dap_test_item(cf, zxid_mk_dap_testop(cf, 0, /* DN from ID-WSF */ "objecttype=svcprofile", 0, /* all attributes */ 1, /* chase symlinks */ ZXID_DAP_SCOPE_SUBTREE, 0, /* no size limit */ 0, /* no time limit */ 0), /* return data */ 0, /* regular data entries */ 0), /* No predefined operation */ zxid_mk_dap_query_item(cf, zxid_mk_dap_select(cf, 0, /* DN from ID-WSF */ "objecttype=svcprofile", 0, /* all attributes */ 1, /* chase symlinks */ ZXID_DAP_SCOPE_SUBTREE, 0, /* no size limit */ 0, /* no time limit */ 0), /* return data */ 0, /* regular data entries */ 0, /* No predefined operation */ 0, /* No sorting. */ 0, /* No changed since specification. */ 0, /* Do not include LDAP common attributes. */ 0, /* Start from first result (offset == 0) */ 0, /* Return all results (count == 0) */ 0, /* Do not request snapshot */ 0, /* Do not refer to snapshot */ 0), /* No contingent item ID reference */ zxid_mk_dap_subscription(cf, "subsID", 0, /* No item ID reference */ zxid_mk_dap_resquery(cf, zxid_mk_dap_select(cf, 0, /* DN from ID-WSF */ "objecttype=svcprofile", 0, /* all attributes */ 1, /* chase symlinks */ ZXID_DAP_SCOPE_SUBTREE, 0, /* no size limit */ 0, /* no time limit */ 0), /* return data */ 0, /* regular data entries */ 0, /* No predefined operation */ 0, /* No sorting. */ 0, /* No changed since specification. */ 0, /* Do not include LDAP common attributes. */ 0), /* No contingent item ID reference */ 0, /* No notification aggregation spec. */ 0, /* No notification trigger spec. */ 0, /* Subscription starts immediately. */ 0, /* Subscription never expires. */ 1, /* Include changed data in the notifications. */ 0, /* Use notification reference for administrative notifications. */ "http://host/notif_sink") ); 13.4.3 zxid_mk_dap_query() ~~~~~~~~~~~~~~~~~~~~~~~~~~ struct zx_dap_Query_s* zxid_mk_dap_query(struct zxid_conf* cf, struct zx_dap_TestItem_s* tis, struct zx_dap_QueryItem_s* qis, struct zx_dap_Subscription_s* subs); 13.4.4 zxid_mk_dap_query_item() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ struct zx_dap_QueryItem_s* zxid_mk_dap_query_item(struct zxid_conf* cf, struct zx_dap_Select_s* sel, char* objtype, char* predef, char* sort, char* changed_since, int incl_common_attrs, int offset, int count, char* setreq, char* setid, char* contingent_itemidref); sel:: Selection expression, see zxid_mk_dap_select(). objtype:: Either "entry", or "_Subsciption". The former is used for searching and manipulating the normal attribute data while the latter is used for manipulating subscription objects. Specifying NULL selects "entry". predef:: Predefined serverside operation identifier. This is server dependent, but you can think of it as a stored procedure. If you pass predef, it is common to leave sel and objtype as NULL and vice versa, i.e. you should pass NULL as predef unless you know your server to expect otherwise. sort:: String specifying sorting of the result set. See ID-DAP specification for further details. N.B. Not all servers support sorting. Pass NULL if you do not need sorting. changed_since:: LDAP date time string specifying that only entries that have changed since specified moment should be returned. incl_common_attrs:: If 1 (true), "common" LDAP attributes such as modificationtime are included. If you do not need these attributes you can save the server some work and also some network transmission overhead by not requesting these attributes, i.e. pass 0 (false). offset:: If search matches multiple entries, the zero based index of the first entry to return. Pass 0 to get the beginning of the result set. count:: Maximum number of entries to return in the response. 0 means return all. +offset+ and +count+ allow pagination through large result set. N.B. +count+ is very different from +sizelimit+ that you may specify in zxid_mk_dap_select(). The latter causes the actual backend search operation to abort or return partial result set if the result would be too large and is unsuitable for pagination. setreq:: Request creation of a result set snapshot for pagination. N.B. It is possible to paginate through large result set even without creating a snapshot, but the results may be inconsistent because the underlying data may change between queries that paginate through it. Creating a snapshot avoids this problem. Whether pagination with or without snapshot is cheaper depends on the backend implementation. +setreq+ is specified on the first query that referes to the snapshot. The subsequent queries referring to the same snapshot will specify +setid+. The two are mutually exclusive: if +setid+ is specified the +setreq+ must be NULL. setid:: If you are paginating through a result set snapshot created using +setreq+, then you must specify +setid+ in subsequent queries that refer to same snapshot. When specifying +setreq+, you muse leave +setid+ as NULL. contingent_itemidref:: A query item can be made contingent on a test item, i.e. the query will only be made if the test succeeded. If you want this, you must pass the item ID of the test item here. Passing NULL means that no such dependency exists. N.B. The server side implementation of the tests may actually require a query to be made anyway. 13.4.5 zxid_mk_dap_select() ~~~~~~~~~~~~~~~~~~~~~~~~~~~ struct zx_dap_Select_s* zxid_mk_dap_select(struct zxid_conf* cf, char* dn, char* filter, char* attributes, int deref_aliases, int scope, int sizelimit, int timelimit, int typesonly); dn:: Distinguished or relative distinguished name. Since ID-DAP usually uses the identity conveyed using ID-WSF headers to determine the distinguished name, it is common to pass simply a NULL. filter:: LDAP filter to apply. NULL if none. attributes:: List of attributes to return. NULL means return all attributes. deref_aliases:: Boolean: whether the server should chase any "symlinks", i.e. an entry may appear at some location as an alias that is just a pointer to the real location of the entry. This is usually what you want so pass 1 (true). scope:: The scope of the ID-DAP search. ZXID_DAP_SCOPE_BASE (0):: Only what is pointed to by DN, e.g. one entry. The default. ZXID_DAP_SCOPE_SINGLE (1):: Single level of directory right under DN. ZXID_DAP_SCOPE_SUBTREE (2):: Full subtree search under the DN. sizelimit:: Maximum number of entries to return. 0 means no limit. This is intended to stop the server from accidentally performing expensive queries. N.B. +sizelimit+ is different from +count+, see zxid_mk_dap_query_item(), the latter is meant for pagination of a large result set without aborting it. timelimit:: Maximum number of seconds to spend in the search. 0 means no limit. typesonly:: If true, only attribute names are returned, without their values. This allows an existence test to be performed without passing the values over the network. Usually you want the values so you would pass 0 (false). 13.4.6 zxid_mk_dap_test_item() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ struct zx_dap_TestItem_s* zxid_mk_dap_test_item(struct zxid_conf* cf, struct zx_dap_TestOp_s* tstop, char* objtype, char* predef); tstop:: See zxid_mk_dap_testop(). objtype:: See +objtype+ in zxid_mk_dap_query_item(). predef:: See +predef+ in zxid_mk_dap_query_item(). 13.4.7 zxid_mk_dap_testop() ~~~~~~~~~~~~~~~~~~~~~~~~~~~ struct zx_dap_TestOp_s* zxid_mk_dap_testop(struct zxid_conf* cf, char* dn, char* filter, char* attributes, int deref_aliases, int scope, int sizelimit, int timelimit, int typesonly); See description of zxid_mk_dap_select(). For ID-DAP protocol the Select and TestOp are defined to be the same. However, this need not be the case for Data Services Template (DST) based services in general. Hence, the data types for Select and TestOp are different (although very similar) and two separate constructors are needed. 13.4.8 zxid_mk_dap_subscription() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ struct zx_dap_Subscription_s* zxid_mk_dap_subscription(struct zxid_conf* cf, char* subsID, char* itemidref, struct zx_dap_ResultQuery_s* rq, char* aggreg, char* trig, char* starts, char* expires, int incl_data, char* admin_notif, char* notify_ref); subsID:: Subscription ID itemidref:: When subscribing to data described by a query item, create item, or modify item, the reference to the relevant item. NULL if no such item exists, in which case +rq+ is usually specified. rq:: Result query that identifies the data of interest for the subscription. See zxid_mk_dap_resquery(). Pass NULL if the data is identified otherwise, e.g. via +itemidref+. aggreg:: Notification aggregation mode. Implementation dependent. Pass NULL. Notification aggregation is an optimization where some notification may be delayed a little so that it can be sent more optimally in same message with other notifications that may happen a little later. This functionality need not be supported by the backend implementations. trig:: Implementation dependent notification triggers. Pass NULL. starts:: Start date time of the subscription. NULL means subscription starts immediately. expires:: End data time of the subscription. NULL means the subscription will not expire. incl_data:: If 1 (true), the notifications resulting from the subscription will contain the changed data (push model). If 0 (false), the notification will just say that something changed, but interested party will need to perform a separate query to retrieve the data (pull model). admin_notif:: Administrative notification address. If NULL, the +notify_ref+ will be used for administrative notifications as well. notify_ref:: Notification address. 13.4.9 zxid_mk_dap_resquery() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ struct zx_dap_ResultQuery_s* zxid_mk_dap_resquery(struct zxid_conf* cf, struct zx_dap_Select_s* sel, char* objtype, char* predef, char* sort, int incl_common_attr, char* changed_since, char* contingent_itemidref); See description of zxid_mk_dap_query_item(). 13.5 ID Messaging Interface --------------------------- 13.6 ID Geo Location Interface ------------------------------ 13.7 Contact Book Interface --------------------------- 13.8 People Service Interface ----------------------------- 13.9 Interface to Conor's Demo Media Service -------------------------------------------- 13.10 ID-SIS Data Service for HR-XML ------------------------------------ HR-XML defines a XML document format that can be used for data interchange in the Human Resources world. The essence of HR-XML is to represent CV (Curriculum Vitae, a.k.a. Resume) in structured form. At the moment (June 2007) HR-XML does not define any standard way to pass these documents around. In CV 2007 conference organized by EIfE-L in Paris, an idea was canvassed: use Liberty Data Service to exchange HR-XML documents. I tried to implement such service during the conference, but only got it working at the air port after the conference. This service is a good example of how to apply ZXID to implement your own services. *Files* sg/hr-xml-sampo.sg - A slightly modified version of HR-XML schema sg/id-hrxml.sg - Liberty DST 2.1 based data service for HR-XML zxidhrxmlwsc.c - Web Services Client for HR-XML zxidhrxmlwsp.c - Web Services Provider for HR-XML *Running demo* 1. Set up your /etc/hosts. At least you need sp1.zxidsp.org and you may also need your IdPs domain name (e.g. idp.symdemo.com). 2. Start your IdP and DS (not supplied with ZXID) cd /opt/SYMfiam/std conf/test-idp3/start.sh start log Next you need to cause the id-hr-xml services to be registered in the discovery service. This depends on product. (*** fix) Then you need to create an association for id-hr-xml service for some test user. 3. Start the web services client mini_httpd -p 8443 -c 'zxid*' -S -E zxid.pem -l tmp/mini.stderr & tail -f tmp/zxid.stderr& 4. Start the web services provider mini_httpd -p 8444 -c 'zxid*' -S -E zxid.pem -l tmp/mini2.stderr & tail -f tmp/zxid2.stderr& 5. Start browsing from https://sp1.zxidsp.org:8443/zxidhrxmlwsc?o=E 6. Login using the test user that has the association 7. Paste <Candidate> element in HR-XML Data form field and click Create. This creates a Candidate record in the WSP. 8. Click Query. This queries the WSP for the record we saved in previous step. You should see the record appear in the HR-XML Response field. http://s-idp.liberty-iop.org:8881/N <<if: ZXIDBOOK>> <<else: >> 96 License ========== Copyright (c) 2006-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved. Author: Sampo Kellomäki (sampo@iki.fi) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 96.2 Specification IPR ---------------------- ZXID is based on open SAML and Liberty specifications. The parties that have developed these specifications, including Symlabs, have made Royalty Free (RF) licensing commitment. Please ask OASIS and Liberty Alliance for the specifics of their IPR policies and IPR disclosures. Some protocols, such as WS-Trust and WS-Federation enjoy Microsoft's pledge<<footnote: If you have a reference to where this pledge can be found, please let me know so it can be included here.>> that they will not sue you even if you implement these specifications. You should evaluate yourself whether this is good enough for your situation. <> <> <<notapath: TCP/IP a.k.a xBSD/Unix n/a Perl/mod_perl PHP/mod_php Java/Tomcat>> <<EOF: >> <<fi: >>