/* zxidpep.c  -  Handwritten functions for XACML Policy Enforcement Point
 * Copyright (c) 2010-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
 * Copyright (c) 2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
 * Author: Sampo Kellomaki (sampo@iki.fi)
 * This is confidential unpublished proprietary source code of the author.
 * NO WARRANTY, not even implied warranties. Contains trade secrets.
 * Distribution prohibited unless authorized in writing.
 * Licensed under Apache License 2.0, see file COPYING.
 * $Id: zxidpep.c,v 1.10 2010-01-08 02:10:09 sampo Exp $
 *
 * 24.8.2009, created --Sampo
 * 10.10.2009, added zxid_az() family --Sampo
 * 12.2.2010,  added locking to lazy loading --Sampo
 * 31.5.2010,  generalized to several PEPs model --Sampo
 * 7.9.2010,   merged patches from Stijn Lievens --Sampo
 * 10.1.2011,  added TrustPDP support --Sampo
 *
 * See also: zxid_simple_ab_pep() in zxidpdp.c, zxidwsc.c, zxidwsp.c
 */

#include "platform.h"
#include "errmac.h"
#include "zxid.h"
#include "zxidpriv.h"
#include "zxidconf.h"
#include "saml2.h"
#include "tas3.h"
#include "c/zx-const.h"
#include "c/zx-ns.h"
#include "c/zx-data.h"
#include "c/zx-e-data.h"

#if 0
#define XS_STRING "xs:string"
#else
#define XS_STRING "http://www.w3.org/2001/XMLSchema#string"
#endif

/*() Check for duplicate attribute in XACML attribute chain
 * for nth occurance of named attribute.
 *
 * - NULL or zero length name will match any
 * - minus one (-1) as either length field will cause strlen() to be done
 * - the index n is one based
 *
 * *Arguments*
 *
 * xac_at_list:: List of (already existing) xac attributes to scan, potentially NULL.
 * name_len:: Length of the attribute name, or 0 if no matching by attribute name is desired
 * name:: attribute name to match (or 0)
 * n:: Howmanieth instance of the matching attribute is desired. 1 means first.
 * return:: Data structure representing the matching attribute.
 */

/* Called by:  zxid_pepmap_extract x4 */
static struct zx_xac_Attribute_s* zxid_find_xac_attribute(struct zx_xac_Attribute_s* xac_at_list, int name_len, char* name, int n)
{
  struct zx_xac_Attribute_s* at;
  if (!name) { name_len = 0; name = ""; }
  if (name_len == -1 && name) name_len = strlen(name);
  if (!xac_at_list) {
    DD("No xac attribute list when looking for attribute name(%.*s) n=%d", name_len, name, n);
    return 0;
  }
  for (at = xac_at_list;
       at;
       at = (struct zx_xac_Attribute_s*)at->gg.g.n) {
    if (at->gg.g.tok != zx_xac_Attribute_ELEM)
      continue;
    if ((name_len ? (at->AttributeId
		     && at->AttributeId->g.len == name_len
		     && !memcmp(at->AttributeId->g.s, name, name_len)) : 1)
	) {
      --n;
      if (!n)
	return at;
    }
  }
  return 0;
}

/*() Extract attributes from session pool according to pepmap, usually the PEPMAP
 * configuration variable.
 *
 * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
 *     set according to your situation.
 * cgi:: if non-null, will receive error and status codes
 * ses:: all attributes are obtained from the session. You may wish
 *     to add additional attributes that are not known by SSO.
 * pepmap:: The map used to extract the attributes from the pool to the XACML request
 * subj:: Value-result parameter. Linked list of subject attributes.
 * rsrc:: Value-result parameter. Linked list of resource attributes.
 * act::  Value-result parameter. Linked list of action attributes (usually just one attribute).
 * env::  Value-result parameter. Linked list of environment attributes.
 */

/* Called by:  zxid_call_trustpdp, zxid_pep_az_base_soap_pepmap, zxid_pep_az_soap_pepmap */
static void zxid_pepmap_extract(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, struct zxid_map* pepmap, struct zx_xac_Attribute_s** subj, struct zx_xac_Attribute_s** rsrc, struct zx_xac_Attribute_s** act, struct zx_xac_Attribute_s** env)
{
  struct zxid_map* map;
  struct zxid_attr* at;
  struct zxid_attr* av;
  struct zx_xac_Attribute_s* xac_at;
  char* name;

  for (at = ses?ses->at:0; at; at = at->n) {
    map = zxid_find_map(pepmap, at->name);
    if (!map) {
      D("ATTR(%s)=VAL(%s): Ignored due to no matching PEPMAP", at->name, at->val);
      continue;
    }

    if (map->rule == ZXID_MAP_RULE_DEL) {
      D("attribute(%s) filtered out by del rule in PEPMAP", at->name);
      continue;
    }
    
    at->map_val = zxid_map_val(cf, ses, 0, map, at->name, at->val);
    
    if (!at->map_val || !at->map_val->len || !at->map_val->s) {
      if (cf->az_opt & 0x02) {
	INFO("attribute(%s) with empty value passed due to AZ_OPT=0x02", at->name);
	continue;
      } else {
	INFO("attribute(%s) filtered out due to empty value", at->name);
	continue;
      }
    }

    if (map->dst && *map->dst && map->src && map->src[0] != '*') {
      D("renaming(%s) to(%s) orig_val(%s) map_val(%.*s)", at->name, map->dst, at->val, at->map_val->len, at->map_val->s);
      name = map->dst;
    } else {
      name = at->name;
    }
    xac_at = zxid_mk_xacml_simple_at(cf, 0,
				     zx_dup_str(cf->ctx, name),
				     zx_dup_str(cf->ctx, XS_STRING),
				     at->issuer, at->map_val);
    if (map->ns && *map->ns) {
      if (!strcmp(map->ns, "subj")) {
	if (cf->az_opt & 0x04) {
	  DD("Dup allowed by AZ_OPT=0x04");
	} else if (zxid_find_xac_attribute(*subj, -1, at->name, 1)) {
	  INFO("attribute(%s) filtered out as duplicate", at->name);
	  continue;
	}
	ZX_NEXT(xac_at) = (void*)*subj;
	*subj = xac_at;
      } else if (!strcmp(map->ns, "rsrc")) {
	if (cf->az_opt & 0x04) {
	  DD("Dup allowed by AZ_OPT=0x04");
	} else if (zxid_find_xac_attribute(*rsrc, -1, at->name, 1)) {
	  INFO("attribute(%s) filtered out as duplicate", at->name);
	  continue;
	}
	ZX_NEXT(xac_at) = (void*)*rsrc;
	*rsrc = xac_at;
      } else if (!strcmp(map->ns, "act")) {
	if (cf->az_opt & 0x04) {
	  DD("Dup allowed by AZ_OPT=0x04");
	} else if (zxid_find_xac_attribute(*act, -1, at->name, 1)) {
	  INFO("attribute(%s) filtered out as duplicate", at->name);
	  continue;
	}
#if 0
	*act = xac_at;  /* there can be only one */
#else
        ZX_NEXT(xac_at) = (void*)*act;
        *act = xac_at;  /* We can have multiple attributes in the action section */
#endif
      } else if (!strcmp(map->ns, "env")) {
	if (cf->az_opt & 0x04) {
	  DD("Dup allowed by AZ_OPT=0x04");
	} else if (zxid_find_xac_attribute(*env, -1, at->name, 1)) {
	  INFO("attribute(%s) filtered out as duplicate", at->name);
	  continue;
	}
	ZX_NEXT(xac_at) = (void*)*env;
	*env = xac_at;
      } else {
	ERR("PEPMAP unknown ns(%s). Valid values are subj, rsrc, act, and env.", map->ns);
      }
    } else {
      ERR("PEPMAP entry lacks ns %p", map->ns);
    }
    
    for (av = at->nv; av; av = av->n) {
      av->map_val = zxid_map_val(cf, ses, 0, map, at->name, av->val);
      xac_at = zxid_mk_xacml_simple_at(cf, 0,
				       zx_dup_str(cf->ctx, name),
				       zx_dup_str(cf->ctx, XS_STRING),
				       at->issuer, at->map_val);
      if (map->ns && *map->ns) {
	if (!strcmp(map->ns, "subj")) {
	  ZX_NEXT(xac_at) = (void*)*subj;
	  *subj = xac_at;
	} else if (!strcmp(map->ns, "rsrc")) {
	  ZX_NEXT(xac_at) = (void*)*rsrc;
	  *rsrc = xac_at;
	} else if (!strcmp(map->ns, "act")) {
	  ERR("PEPMAP: Only one XACML action attribute allowed %d", 0);
	} else if (!strcmp(map->ns, "env")) {
	  ZX_NEXT(xac_at) = (void*)*env;
	  *env = xac_at;
	} else {
	  ERR("PEPMAP unknown ns(%s). Valid values are subj, rsrc, act, and env.", map->ns);
	}
      } else {
	ERR("PEPMAP entry lacks ns %p", map->ns);
      }
    }
  }
  
  if (!*act) {
    *act = zxid_mk_xacml_simple_at(cf, 0,
	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:action-id"),
	     zx_dup_str(cf->ctx, XS_STRING),
	     0,
	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:implied-action"));
  }
}

/* ============== Policy Enforcement Point, Authorization Decision Query ============== */

/*() Call Policy Decision Point (PDP) to obtain an authorization decision
 * about a contemplated action on a resource. The attributes from the session
 * pool, as filtered by PEPMAP are fed to the PDP as inputs
 * for the decision. The call is using XACML SAML profile over SOAP.
 *
 * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
 *     set according to your situation.
 * cgi:: if non-null, will receive error and status codes
 * ses:: all attributes are obtained from the session. You may wish
 *     to add additional attributes that are not known by SSO.
 * pdp_url:: URL of the PDP to contact
 * subj:: Linked list of subject attributes.
 * rsrc:: Linked list of resource attributes.
 * act::  Linked list of action attributes (usually just one attribute).
 * envi::  Linked list of environment attributes.
 * returns:: SAML Response as data structure or null upon error.
 */

/* Called by:  zxid_call_trustpdp, zxid_pep_az_base_soap_pepmap, zxid_pep_az_soap_pepmap */
static struct zx_sp_Response_s* zxid_az_soap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url, struct zx_xac_Attribute_s* subj, struct zx_xac_Attribute_s* rsrc, struct zx_xac_Attribute_s* act, struct zx_xac_Attribute_s* envi)
{
  X509* sign_cert;
  EVP_PKEY* sign_pkey;
  struct zxsig_ref refs;
  struct zx_root_s* r;
  struct zx_e_Header_s* hdr;
  struct zx_e_Body_s* body;
  struct zx_wsse_Security_s* sec;
  struct zx_str* ss;
  struct zx_sp_Response_s* resp;

  hdr = zx_NEW_e_Header(cf->ctx,0);
#if 0
  hdr->Action = zx_NEW_a_Action(cf->ctx, &hdr->gg);
  //zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:2.0:profile:saml2.0:v2:schema:protocol:cd-01"));
  zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "urn:oasis:xacml:2.0:saml:protocol:schema:os"));

  //zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "SAML2XACMLAuthzRequest"));
  //zx_add_content(cf->ctx, &hdr->Action->gg, zx_dup_str(cf->ctx, "http://ws.apache.org/axis2/TestPolicyPortType/authRequestRequest"));
  hdr->Action->mustUnderstand = zx_ref_str(cf->ctx, XML_TRUE);
  hdr->Action->actor = zx_ref_str(cf->ctx, SOAP_ACTOR_NEXT);
#endif

  /* Add our own token so PDP can do whatever PEP can (they are considered to be
   * part of the same entity). This is TAS3 specific hack. */

  if (!(cf->az_opt & 0x01)) {
    sec = hdr->Security = zx_NEW_wsse_Security(cf->ctx, &hdr->gg);
    sec->actor = zx_ref_attr(cf->ctx, &sec->gg, zx_e_actor_ATTR, SOAP_ACTOR_NEXT);
    sec->mustUnderstand = zx_ref_attr(cf->ctx, &sec->gg, zx_e_mustUnderstand_ATTR, XML_TRUE);
    sec->Timestamp = zx_NEW_wsu_Timestamp(cf->ctx, &sec->gg);
    sec->Timestamp->Created = zx_NEW_wsu_Created(cf->ctx, &sec->Timestamp->gg);
    zx_add_content(cf->ctx, &sec->Timestamp->Created->gg, zxid_date_time(cf, time(0)));
    sec->Assertion = ses->tgta7n;
    zx_reverse_elem_lists(&sec->gg);
    D("tgta7n=%p", ses->tgta7n);
  }
  /* Prepare request according to the version */

  body = zx_NEW_e_Body(cf->ctx,0);
  if (!strcmp(cf->xasp_vers, "xac-soap")) {
    ZX_ADD_KID(body, xac_Request, zxid_mk_xac_az(cf, &body->gg, subj, rsrc, act, envi));
#if 0
    /* *** xac:Response does not have signature field */
    if (cf->sso_soap_sign) {
      ZERO(&refs, sizeof(refs));
      refs.id = body->xac_Request->ID;
      refs.canon = zx_EASY_ENC_SO_xac_Request(cf->ctx, body->xac_Request);
      if (zxid_lazy_load_sign_cert_and_pkey(cf, &sign_cert, &sign_pkey, "use sign cert az xac-soap")) {
	body->xac_Request->Signature = zxsig_sign(cf->ctx, 1, &refs, sign_cert, sign_pkey);
	zx_add_kid(&body->xac_Request->gg, &body->xac_Request->Signature->gg);
      }
      zx_str_free(cf->ctx, refs.canon);
    }
#endif
  } else if (!strcmp(cf->xasp_vers, "2.0-cd1")) {
    ZX_ADD_KID(body, xaspcd1_XACMLAuthzDecisionQuery, zxid_mk_az_cd1(cf, subj, rsrc, act, envi));
    if (cf->sso_soap_sign) {
      ZERO(&refs, sizeof(refs));
      refs.id = &body->xaspcd1_XACMLAuthzDecisionQuery->ID->g;
      refs.canon = zx_easy_enc_elem_sig(cf, &body->xaspcd1_XACMLAuthzDecisionQuery->gg);
      if (zxid_lazy_load_sign_cert_and_pkey(cf, &sign_cert, &sign_pkey, "use sign cert az cd1")) {
	body->xaspcd1_XACMLAuthzDecisionQuery->Signature
	  = zxsig_sign(cf->ctx, 1, &refs, sign_cert, sign_pkey);
	zx_add_kid_after_sa_Issuer(&body->xaspcd1_XACMLAuthzDecisionQuery->gg, &body->xaspcd1_XACMLAuthzDecisionQuery->Signature->gg);
      }
      zx_str_free(cf->ctx, refs.canon);
    }
  } else {
    ZX_ADD_KID(body, XACMLAuthzDecisionQuery, zxid_mk_az(cf, subj, rsrc, act, envi));
    if (cf->sso_soap_sign) {
      ZERO(&refs, sizeof(refs));
      refs.id = &body->XACMLAuthzDecisionQuery->ID->g;
      refs.canon = zx_easy_enc_elem_sig(cf, &body->XACMLAuthzDecisionQuery->gg);
      if (zxid_lazy_load_sign_cert_and_pkey(cf, &sign_cert, &sign_pkey, "use sign cert az")) {
	body->XACMLAuthzDecisionQuery->Signature
	  = zxsig_sign(cf->ctx, 1, &refs, sign_cert, sign_pkey);
	zx_add_kid_after_sa_Issuer(&body->XACMLAuthzDecisionQuery->gg, &body->XACMLAuthzDecisionQuery->Signature->gg);
      }
      zx_str_free(cf->ctx, refs.canon);
    }
  }

  /* Perform the network I/O for the call (connect to PDP) */

#if 0
  //ss = zx_ref_str(cf->ctx, "https://idpdemo.tas3.eu:8443/zxididp?o=S");
  // http://192.168.136.42:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpSoap12Endpoint/
  // http://192.168.1.27:1104/axis2/services/TestPolicy?wsdl
  // http://192.168.1.66:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpEndpoint/
  ss = zx_ref_str(cf->ctx, "http://192.168.1.27:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpEndpoint/");
  //ss = zx_ref_str(cf->ctx, "");
  //ss = zx_ref_str(cf->ctx, "http://192.168.1.66:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpEndpoint/");
  //ss = zx_ref_str(cf->ctx, "http://192.168.1.27:1104/axis2/services/TestPolicy.PERMISAuthzServerHttpSoap12Endpoint/");
#else
  ss = zx_ref_str(cf->ctx, pdp_url);
#endif
  r = zxid_soap_call_hdr_body(cf, ss, hdr, body);
  //r = zxid_idp_soap(cf, cgi, ses, idp_meta, ZXID_MNI_SVC, body);
  if (!r || !r->Envelope || !r->Envelope->Body || !r->Envelope->Body->Response) {
    ERR("Missing Response or other essential element %p %p %p %p", r, r?r->Envelope:0, r && r->Envelope?r->Envelope->Body:0, r && r->Envelope && r->Envelope->Body ? r->Envelope->Body->Response:0);
    return 0;
  }

  resp = r->Envelope->Body->Response;

  /* Parse response from the PDP. */

  if (!zxid_saml_ok(cf, cgi, resp->Status, "AzResp")) {
    ERR("Response->Status no OK (%p)", resp->Status);
    return 0;
  }
  if (!resp->Assertion) {
    ERR("No Assertion in the Response (%p)", resp);
    return 0;
  }
  
  return resp;
}

/*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
 * about a contemplated action on a resource. The attributes from the session
 * pool, as filtered by PEPMAP are fed to the PDP as inputs
 * for the decision. The call is using XACML SAML profile over SOAP.
 *
 * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
 *     set according to your situation.
 * cgi:: if non-null, will receive error and status codes
 * ses:: all attributes are obtained from the session. You may wish
 *     to add additional attributes that are not known by SSO.
 * pdp_url:: URL of the PDP to contact
 * pepmap:: The map used to extract the attributes from the pool to the XACML request
 * returns:: 0 on error or deny (for any reason, e.g. indeterminate); in case of
 *     permit returns <xac:Response> as string, allowing the obligations to be extracted.
 *
 * For simpler API, see zxid_az() family of functions.
 */

/* Called by:  zxid_pep_az_soap, zxid_query_ctlpt_pdp, zxid_simple_ab_pep, zxid_simple_ses_active_cf */
char* zxid_pep_az_soap_pepmap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url, struct zxid_map* pepmap, const char* lk)
{
  struct zx_xac_Attribute_s* subj = 0;
  struct zx_xac_Attribute_s* rsrc = 0;
  struct zx_xac_Attribute_s* act = 0;
  struct zx_xac_Attribute_s* env = 0;
  struct zx_str* ss;
  struct zx_sp_Response_s* resp;
  struct zx_sa_Statement_s* stmt;
  struct zx_xasa_XACMLAuthzDecisionStatement_s* az_stmt;
  struct zx_xasacd1_XACMLAuthzDecisionStatement_s* az_stmt_cd1;
  struct zx_elem_s* decision = 0;
  char* p;

  if (cf->log_level>0)
    zxlog(cf, 0, 0, 0, 0, 0, 0, ses?ZX_GET_CONTENT(ses->nameid):0, "N", "W", "AZSOAP", ses?ses->sid:0, " ");
  
  if (!pdp_url || !*pdp_url) {
    ERR("%s: No PDP_URL or PDP_CALL_URL set. Deny. %p", lk, pdp_url);
    return 0;
  }

  zxid_pepmap_extract(cf, cgi, ses, pepmap, &subj, &rsrc, &act, &env);
  resp = zxid_az_soap(cf, cgi, ses, pdp_url, subj, rsrc, act, env);
  if (!resp || !resp->Assertion) {
    ERR("%s: DENY due to malformed authorization response from PDP. Either no response or response lacking assertion. %p", lk, resp);
    return 0;
  }
  
  az_stmt = resp->Assertion->XACMLAuthzDecisionStatement;
  if (az_stmt && az_stmt->Response && az_stmt->Response->Result) {
    decision = az_stmt->Response->Result->Decision;
    if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
      ss = zx_easy_enc_elem_opt(cf, &az_stmt->Response->gg);
      if (!ss || !ss->len) goto nomem;
      p = ss->s;
      INFO("%s: PERMIT found in azstmt len=%d", lk, ss->len);
      ZX_FREE(cf->ctx, ss);
      return p;
    }
  }
  az_stmt_cd1 = resp->Assertion->xasacd1_XACMLAuthzDecisionStatement;
  if (az_stmt_cd1 && az_stmt_cd1->Response && az_stmt_cd1->Response->Result) {
    decision = az_stmt_cd1->Response->Result->Decision;
    if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
      ss = zx_easy_enc_elem_opt(cf, &az_stmt_cd1->Response->gg);
      if (!ss || !ss->len) goto nomem;
      p = ss->s;
      ZX_FREE(cf->ctx, ss);
      INFO("%s: PERMIT found in azstmt_cd1 len=%d", lk, ss->len);
      ZX_FREE(cf->ctx, ss);
      return p;
    }
  }
  stmt = resp->Assertion->Statement;
  if (stmt && stmt->Response && stmt->Response->Result) {  /* Response here is xac:Response */
    decision = stmt->Response->Result->Decision;
    if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
      ss = zx_easy_enc_elem_opt(cf, &stmt->Response->gg);
      if (!ss || !ss->len) goto nomem;
      p = ss->s;
      INFO("%s: PERMIT found in stmt len=%d", lk, ss->len);
      ZX_FREE(cf->ctx, ss);
      return p;
    }
  }
  /*if (resp->Assertion->AuthzDecisionStatement) {  }*/
  if (decision) {
    if (ZX_CONTENT_EQ_CONST(decision, "Deny")) {
      INFO("%s: DENY found in stmt", lk);
      return 0;
    } else {
      INFO("%s: DENY due to Other(%.*s) (treated as Deny) found in stmt", lk, ZX_GET_CONTENT_LEN(decision), ZX_GET_CONTENT_S(decision));
      return 0;
    }
  }
  ERR("%s: DENY due to error or no xac:Response from PDP %p %p %p", lk, az_stmt,az_stmt_cd1,stmt);
  return 0;
 nomem:
  ERR("%s: DENY due memory allocation error. %p", lk, ss);
  return 0;
}

/*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
 * about a contemplated action on a resource. The attributes from the session
 * pool, as filtered by PEPMAP are fed to the PDP as inputs
 * for the decision. The call is using XACML SAML profile over SOAP.
 *
 * This is similar to zxid_pep_az_soap_pepmap() with the difference that the <xac:Response>
 * element is returned even in the deny and indeterminate cases (null
 * is still returned if there was an error). Effectively this +base+
 * form does not make judgement about whether <xac:Response> means
 * permit, deny, or something else.
 *
 * You should use this function if the Deny message contains interesting
 * obligations (normally it does not).
 *
 * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
 *     set according to your situation.
 * cgi:: if non-null, will receive error and status codes
 * ses:: all attributes are obtained from the session. You may wish
 *     to add additional attributes that are not known by SSO.
 * pdp_url:: URL of the PDP to contact
 * pepmap:: The map used to extract the attributes from the pool to the XACML request
 * returns:: 0 on error; in case of deny (or indeterminate, etc.) as well as
 *     permit returns <xac:Response> as string, allowing the obligations to be extracted.
 *
 * For simpler API, see zxid_az_base() family of functions.
 */

/* Called by:  zxid_pep_az_base_soap */
char* zxid_pep_az_base_soap_pepmap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url, struct zxid_map* pepmap)
{
  struct zx_xac_Attribute_s* subj = 0;
  struct zx_xac_Attribute_s* rsrc = 0;
  struct zx_xac_Attribute_s* act = 0;
  struct zx_xac_Attribute_s* env = 0;
  char* res;
  struct zx_str* ss;
  struct zx_sp_Response_s* resp;
  struct zx_sa_Statement_s* stmt;
  struct zx_xasa_XACMLAuthzDecisionStatement_s* az_stmt;
  struct zx_xasacd1_XACMLAuthzDecisionStatement_s* az_stmt_cd1;

  if (cf->log_level>0)
    zxlog(cf, 0, 0, 0, 0, 0, 0, ses?ZX_GET_CONTENT(ses->nameid):0, "N", "W", "AZSOAP", ses?ses->sid:0, " ");
  
  if (!pdp_url || !*pdp_url) {
    ERR("No PDP_URL or PDP_CALL_URL set. Deny. %p", pdp_url);
    return 0;
  }

  zxid_pepmap_extract(cf, cgi, ses, pepmap, &subj, &rsrc, &act, &env);
  resp = zxid_az_soap(cf, cgi, ses, pdp_url, subj, rsrc, act, env);
  if (!resp || !resp->Assertion) {
    ERR("DENY due to malformed authorization response from PDP. Either no response or response lacking assertion. %p", resp);
    return 0;
  }

  az_stmt = resp->Assertion->XACMLAuthzDecisionStatement;
  if (az_stmt && az_stmt->Response) {
    ss = zx_easy_enc_elem_opt(cf, &az_stmt->Response->gg);
    if (!ss || !ss->len)
      return 0;
    res = ss->s;
    ZX_FREE(cf->ctx, ss);
    D("azstmt(%s)", res);
    return res;
  }
  az_stmt_cd1 = resp->Assertion->xasacd1_XACMLAuthzDecisionStatement;
  if (az_stmt_cd1 && az_stmt_cd1->Response) {
    ss = zx_easy_enc_elem_opt(cf, &az_stmt_cd1->Response->gg);
    if (!ss || !ss->len)
      return 0;
    res = ss->s;
    ZX_FREE(cf->ctx, ss);
    D("cd1(%s)", res);
    return res;
  }
  stmt = resp->Assertion->Statement;
  if (stmt && stmt->Response) {  /* Response here is xac:Response */
    ss = zx_easy_enc_elem_opt(cf, &stmt->Response->gg);
    if (!ss || !ss->len)
      return 0;
    res = ss->s;
    ZX_FREE(cf->ctx, ss);
    D("stmt(%s)", res);
    return res;
  }

  D("Missing az related Response element %d",0);
  return 0;
}

/*() Call Policy Decision Point (PDP) to obtain an authorization decision.
 * Uses default PEPMAP to call zxid_pep_az_soap_pepmap(). */

/* Called by:  zxid_az_cf_ses */
char* zxid_pep_az_soap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url) {
  return zxid_pep_az_soap_pepmap(cf, cgi, ses, pdp_url, cf->pepmap, "az_soap");
}

/* Called by:  zxid_az_base_cf_ses */
char* zxid_pep_az_base_soap(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* pdp_url) {
  return zxid_pep_az_base_soap_pepmap(cf, cgi, ses, pdp_url, cf->pepmap);
}

/*int zxid_az_cf_cgi_ses(zxid_conf* cf,  zxid_cgi* cgi, zxid_ses* ses);*/

/*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
 * about a contemplated action on a resource. The attributes from the session
 * pool, as filtered by ~PEPMAP~ are fed to the PDP as inputs
 * for the decision. Session object is passed in as an argument.
 *
 * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
 *     set according to your situation.
 * qs:: if non-null, will resceive error and status codes
 * ses:: all attributes are obtained from the session. You may wish
 *     to add additional attributes that are not known by SSO.
 *     The session object, e.g. from zxid_get_ses()
 * returns:: 0 on deny (for any reason, e.g. indeterminate),  or string
 *     containing the obligations on permit.
 *
 * For simpler API, see zxid_az() family of functions.
 */

/* Called by:  zxcall_main, zxid_az_cf */
char* zxid_az_cf_ses(zxid_conf* cf, const char* qs, zxid_ses* ses)
{
  zxid_cgi cgi;
  char* ret;
  char* url = (cf->pdp_call_url&&*cf->pdp_call_url) ? cf->pdp_call_url : cf->pdp_url;
  D_INDENT("az: ");
  ZERO(&cgi, sizeof(cgi));
  /*zxid_parse_cgi(cf, &cgi, "");  DD("qs(%s) ses=%p", STRNULLCHKD(qs), ses);*/
  if (qs && ses)
    zxid_add_qs2ses(cf, ses, zx_dup_cstr(cf->ctx, qs), 1);
  ret = zxid_pep_az_soap(cf, &cgi, ses, url);
  D_DEDENT("az: ");
  return ret;
}

/* Called by:  zxid_az_base_cf */
char* zxid_az_base_cf_ses(zxid_conf* cf, const char* qs, zxid_ses* ses)
{
  zxid_cgi cgi;
  char* ret;
  char* url = (cf->pdp_call_url&&*cf->pdp_call_url) ? cf->pdp_call_url : cf->pdp_url;
  D_INDENT("azb: ");
  ZERO(&cgi, sizeof(cgi));
  /*zxid_parse_cgi(cf, &cgi, "");  DD("qs(%s) ses=%p", STRNULLCHKD(qs), ses);*/
  if (qs && ses)
    zxid_add_qs2ses(cf, ses, zx_dup_cstr(cf->ctx, qs), 1);
  ret = zxid_pep_az_base_soap(cf, &cgi, ses, url);
  D_DEDENT("azb: ");
  return ret;
}

/*(i) Call Policy Decision Point (PDP) to obtain an authorization decision
 * about a contemplated action on a resource. The attributes from the session
 * pool, as filtered by ~PEPMAP~ are fed to the PDP as inputs
 * for the decision. Session is identified by a session id.
 *
 * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
 *     set according to your situation.
 * qs:: if non-null, will resceive error and status codes
 * sid:: all attributes are obtained from the session. You may wish
 *     to add additional attributes that are not known by SSO. The
 *     session id, such as returned from SSO.
 * returns:: 0 on deny (for any reason, e.g. indeterminate),  or string
 *     containing the obligations on permit.
 *
 * For simpler API, see zxid_az() family of functions.
 */

/* Called by:  zxid_az */
char* zxid_az_cf(zxid_conf* cf, const char* qs, const char* sid)
{
  zxid_ses ses;
  ZERO(&ses, sizeof(ses));
  if (sid && sid[0]) {
    zxid_get_ses(cf, &ses, sid);
    zxid_ses_to_pool(cf, &ses);
  }
  return zxid_az_cf_ses(cf, qs, &ses);
}

/* Called by:  zxid_az_base */
char* zxid_az_base_cf(zxid_conf* cf, const char* qs, const char* sid)
{
  zxid_ses ses;
  ZERO(&ses, sizeof(ses));
  if (sid && sid[0]) {
    zxid_get_ses(cf, &ses, sid);
    zxid_ses_to_pool(cf, &ses);
  }
  return zxid_az_base_cf_ses(cf, qs, &ses);
}

/*() See zxid_az_cf() for description. Only difference is that the configuration
 * is accepted as a string instead of an object. */

/* Called by: */
char* zxid_az(const char* conf, const char* qs, const char* sid)
{
  struct zx_ctx ctx;
  zxid_conf cf;
  zx_reset_ctx(&ctx);
  ZERO(&cf, sizeof(cf));
  cf.ctx = &ctx;
  zxid_conf_to_cf_len(&cf, -1, conf);
  return zxid_az_cf(&cf, qs, sid);
}

/* Called by: */
char* zxid_az_base(const char* conf, const char* qs, const char* sid)
{
  struct zx_ctx ctx;
  zxid_conf cf;
  zx_reset_ctx(&ctx);
  ZERO(&cf, sizeof(cf));
  cf.ctx = &ctx;
  zxid_conf_to_cf_len(&cf, -1, conf);
  return zxid_az_base_cf(&cf, qs, sid);
}

/*() Call Trust Policy Decision Point (PDP) to obtain a trust evaluation and scoring
 *
 * You should use this function if the Deny message contains interesting
 * obligations (normally it does not).
 *
 * cf:: the configuration will need to have ~PEPMAP~ and ~PDP_URL~ options
 *     set according to your situation.
 * cgi:: if non-null, will receive error and status codes
 * ses:: all attributes are obtained from the session. You may wish
 *     to add additional attributes that are not known by SSO.
 * pepmap:: The map used to extract the attributes from the pool to the XACML request
 * start:: Start of query string format buffer of trust options
 * lim:: One past end of trust option buffer
 * returns:: 0 on error; in case of deny (or indeterminate, etc.) as well as
 *     permit returns <xac:Response> as string, allowing the obligations to be extracted.
 */

/* Called by:  zxid_di_query */
int zxid_call_trustpdp(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, struct zxid_map* pepmap, const char* start, const char* lim, zxid_epr* epr)
{
  struct zx_xac_Attribute_s* xac_at;
  struct zx_xac_Attribute_s* subj = 0;
  struct zx_xac_Attribute_s* rsrc = 0;
  struct zx_xac_Attribute_s* act = 0;
  struct zx_xac_Attribute_s* env = 0;
  const char* val;
  const char* sep;
  struct zx_str* ss;
  struct zx_sp_Response_s* resp;
  struct zx_sp_StatusCode_s* sc;
  struct zx_sa_Statement_s* stmt;
  struct zx_xasa_XACMLAuthzDecisionStatement_s* az_stmt;
  struct zx_xasacd1_XACMLAuthzDecisionStatement_s* az_stmt_cd1;
  struct zx_elem_s* decision;
  struct zx_tas3_TrustRanking_s* tr;

  D("option(%.*s)", ((int)(lim-start)), start);

  if (cf->log_level>0)
    zxlog(cf, 0, 0, 0, 0, 0, 0, ses?ZX_GET_CONTENT(ses->nameid):0, "N", "W", "TRUSTPDP", ses?ses->sid:0, " ");
  
  if (!cf->trustpdp_url || !*cf->trustpdp_url) {
    ERR("No TRUSTPDP_URL. Deny. %p", cf->trustpdp_url);
    return 0;
  }

#if 1
  if (epr->Metadata && epr->Metadata->ProviderID) {
    ss = ZX_GET_CONTENT(epr->Metadata->ProviderID);
    D("Using ProviderID(%.*s) as resource-id", ss->len, ss->s);
    rsrc = zxid_mk_xacml_simple_at(cf, 0,
				     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:resource:resource-id"),
				     zx_dup_str(cf->ctx, XS_STRING),
				     0, zx_dup_zx_str(cf->ctx, ss));
    ss = ZX_GET_CONTENT(epr->Metadata->ServiceType);
    xac_at = zxid_mk_xacml_simple_at(cf, 0,
				     zx_dup_str(cf->ctx, "urn:tas3:servicetype"),
				     zx_dup_str(cf->ctx, XS_STRING),
				     0, zx_dup_zx_str(cf->ctx, ss));
    ZX_NEXT(xac_at) = &rsrc->gg.g;
    rsrc = xac_at;
  } else {
    ERR("EPR does not have Metadata or Metadata/ProviderID. resource-id not set %p",epr->Metadata);
  }

  if (ses->nid && *ses->nid) {
#if 0
    /* Pass user as subject. This is TAS3 correct behaviour. */
    subj = zxid_mk_xacml_simple_at(cf, 0,
		zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:subject:subject-id"),
		zx_dup_str(cf->ctx, XS_STRING),
		0,
		zx_dup_str(cf->ctx, ses->nid));
#else
    /* *** Pass service to rank as subject. This is a kludge to work around Jerry's bug. */
    ss = ZX_GET_CONTENT(epr->Metadata->ProviderID);
    D("*** Using ProviderID(%.*s) as subject-id", ss->len, ss->s);
    subj = zxid_mk_xacml_simple_at(cf, 0,
		zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:subject:subject-id"),
		zx_dup_str(cf->ctx, XS_STRING),
		0, zx_dup_zx_str(cf->ctx, ss));
#endif
  } else {
    D("No ses->nid %p", ses->nid);
  }

  act = zxid_mk_xacml_simple_at(cf, 0,
	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:action-id"),
	     zx_dup_str(cf->ctx, XS_STRING),
	     0,
	     zx_dup_str(cf->ctx, "urn:oasis:names:tc:xacml:1.0:action:implied-action"));
#else
  zxid_pepmap_extract(cf, cgi, ses, pepmap, &subj, &rsrc, &act, &env);
#endif
  
  while (start < lim && (start = zx_memmem(start, (lim - start), TAS3_TRUST_INPUT_CTL1, sizeof(TAS3_TRUST_INPUT_CTL1)-1))) {

    val = memchr(start+sizeof(TAS3_TRUST_INPUT_CTL1)-1, '=', lim - (start+sizeof(TAS3_TRUST_INPUT_CTL1)-1));
    if (!val) {
      ERR("Malformed trust option(%.*s)", ((int)(lim-start)), start);
      break;
    }
    ++val;
    sep = memchr(val, '&', lim-val);
    if (!sep) {
      sep = lim;
    }
    
    D("add trust attr(%.*s) val(%.*s)", ((int)(val-1-start)), start, ((int)(sep-val)), val);
    xac_at = zxid_mk_xacml_simple_at(cf, 0,
				     zx_dup_len_str(cf->ctx, val-1-start, start),
				     zx_dup_str(cf->ctx, XS_STRING),
				     0, zx_dup_len_str(cf->ctx, sep-val, val));
    ZX_NEXT(xac_at) = &env->gg.g;
    env = xac_at;
    start = sep;
  }

  resp = zxid_az_soap(cf, cgi, ses, cf->trustpdp_url, subj, rsrc, act, env);
  if (!resp)
    return 0;

  az_stmt = resp->Assertion->XACMLAuthzDecisionStatement;
  if (az_stmt && az_stmt->Response) {
    decision = az_stmt->Response->Result->Decision;
  } else {
    az_stmt_cd1 = resp->Assertion->xasacd1_XACMLAuthzDecisionStatement;
    if (az_stmt_cd1 && az_stmt_cd1->Response) {
      decision = az_stmt_cd1->Response->Result->Decision;
    } else {
      stmt = resp->Assertion->Statement;
      if (stmt && stmt->Response) {  /* Response here is xac:Response */
	decision = stmt->Response->Result->Decision;
      } else {
	D("Missing az related Response element %d",0);
	return 0;
      }
    }
  }
  if (ZX_CONTENT_EQ_CONST(decision, "Permit")) {
    D("Permit %d",0);
    if (resp->Status && resp->Status->StatusCode && resp->Status->StatusCode->StatusCode) {
      /* Second and further layer status codes may contain Trust Rankings */
      for (sc = resp->Status->StatusCode->StatusCode; sc; sc = sc->StatusCode) {
	if (!sc->Value || !sc->Value->g.len < sizeof(TAS3_TRUST_RANKING_CTL1)-1 ||!sc->Value->g.s
	    || memcmp(sc->Value->g.s, TAS3_TRUST_RANKING_CTL1,sizeof(TAS3_TRUST_RANKING_CTL1)-1))
	  continue;
	val = memchr(sc->Value->g.s + sizeof(TAS3_TRUST_RANKING_CTL1)-1, '=', sc->Value->g.len - (sizeof(TAS3_TRUST_RANKING_CTL1)-1));
	if (!val) {
	  ERR("Malformed trust ranking(%.*s)", sc->Value->g.len, sc->Value->g.s);
	  continue;
	}
	
	if (!epr->Metadata)
	  epr->Metadata = zx_NEW_a_Metadata(cf->ctx, &epr->gg);
	if (!epr->Metadata->Trust)
	  epr->Metadata->Trust = zx_NEW_tas3_Trust(cf->ctx, &epr->Metadata->gg);
	tr = zx_NEW_tas3_TrustRanking(cf->ctx, &epr->Metadata->Trust->gg);
	tr->metric = zx_dup_len_attr(cf->ctx, &tr->gg, zx_metric_ATTR,
				     val - sc->Value->g.s, sc->Value->g.s);
	++val;
	tr->val = zx_dup_len_attr(cf->ctx, &tr->gg, zx_val_ATTR,
				  sc->Value->g.len - (val - sc->Value->g.s), val);
      }
    }
    
    INFO("PERMIT found in azstmt len=%d", ss->len);
    ZX_FREE(cf->ctx, ss);
    return 1;
  } else {
    D("Deny %d",0);
    return 0;
  }
}

/* EOF  --  zxidpep.c */
