#include "prima_saml_support.h" #include "prima_logger.h" #include #include #include #include #include using namespace std; using namespace saml; XERCES_CPP_NAMESPACE_USE /** initializes the SAML Library, returns 0 if initialization succeeded, 1 if not */ int initPrimaSAMLSupport(const char* gSchemaDir, int logLevel) { // initialize log4cpp as used by openSAML if(logLevel>=PRIMA_LOG_DEBUG) { log4cpp::Category& category = log4cpp::Category::getRoot(); category.setPriority(log4cpp::Priority::DEBUG); } else { log4cpp::Category& category = log4cpp::Category::getRoot(); category.setPriority(log4cpp::Priority::ERROR); } // initialize SAML, specifying the schema directory SAMLConfig& conf=SAMLConfig::getConfig(); conf.schema_dir = gSchemaDir; conf.compatibility_mode=true; //compatibility to SAML1.0 if (!conf.init()) { return 1; } return 0; } int cleanupPrimaSAMLSupport() { return 0; } /** creates a SAML Request that holds an AuthoirzationDecisionQuery for identity mapping based on the provided * resource for which the mapping is requested, the user's subject DN for whom the mapping is requested and a * set of user attributes that will be attached as evidence statements * Only VOMS FQAN Attributes are supported to date * * New: 20050203: Also adds attributes that do not have the validity indicator set to true!! * (but if validity check is enabled, then no such attributes would be included anyhow, and if we don't * check for validity then this is the desired behavior) */ char * createSAMLQueryAndRequest(char* requestedResource, char* requestorSubject, gss_priv_attribute_set *attributes) { char * retSamlQuery = NULL; gss_attribute * at = NULL; // holds voms attributes during evidence creation /* creating a simple subject based on X509 DN */ auto_ptr_XMLCh name(requestorSubject); SAMLNameIdentifier* nameId = new SAMLNameIdentifier(name.get()); /* the following creates a basicSubject - unable to reuse nameId... leads to incomplete XML * thus creating a new SAMLNameIdentifier object */ SAMLNameIdentifier* nameIdclone = new SAMLNameIdentifier(name.get()); SAMLSubject* basicSubject = new SAMLSubject(nameId); // extending that subject to also hold the certificate path //auto_ptr_XMLCh confMethod("http://www.gridforum.org/ogsa-authz/saml/2004/am/gsi"); //const XMLCh* confMethods[] = { confMethod.get() }; //auto_ptr_XMLCh confData("**** Here goes the cert path according to X509PKIPathv1 ****"); SAMLSubject* subjectWithCertChain = new SAMLSubject(nameIdclone); // SAMLSubject* subjectWithCertChain = new SAMLSubject(nameIdclone, // ArrayIterator(confMethods,1), // confData.get(), NULL); auto_ptr_XMLCh resource(requestedResource); std::vector actionVector; auto_ptr_XMLCh strAction("access_as_local_identity"); SAMLAction* action = new SAMLAction(strAction.get(), XML::OSG_AUTHZ_NS); actionVector.push_back(action); // create assertion vector to hold all assertions std::vector assertionVector; // now walk through attributes and create assertions based on attributes for the evidence field for(int i=0; ipriv_attributes->attribute_count; i++) { at = &(attributes->priv_attributes->attributes[i]); // we only want to process this attribute if the following holds // validity condition removed 20050203: (at->validity_indicator == PRIMA_TRUE) && if ( (!strcmp("VOMSAttribute", at->attribute_type.id_value.string)) ) { char * issuerString = NULL; char * fqanString = NULL; issuerString = (char*) malloc (at->policy_authority.id_value.buffer->length+1); if(!issuerString) { logerr("malloc error on issuerString - ignoring attribute"); continue; } strncpy(issuerString, (char*) at->policy_authority.id_value.buffer->value, at->policy_authority.id_value.buffer->length+1); // VOMS FQAN issuer auto_ptr_XMLCh issuer(issuerString); // we will only look at the first attribute value if(!at->values_list->values[0].buffer) { logerr("attribute holds no value - ignoring attribute"); free(issuerString); continue; } fqanString = (char*) malloc (at->values_list->values[0].buffer->length+1); if(!fqanString) { logerr("malloc error fqanString - ignoring attribute"); free(issuerString); continue; } strncpy(fqanString, (char*) at->values_list->values[0].buffer->value, at->values_list->values[0].buffer->length+1); logit2("including FQAN: ", fqanString); // FQAN value auto_ptr_XMLCh val(fqanString); const XMLCh* vv[] = { val.get() }; auto_ptr_XMLCh attrName("FQAN"); auto_ptr_XMLCh attrNamespace("opensciencegrid:authorization"); SAMLAttribute *att = new SAMLAttribute(attrName.get(), attrNamespace.get(), NULL, 0, ArrayIterator(vv,1)); // make an evidence element and add it to the evidence vector SAMLAttribute* attv[] = { att }; SAMLStatement* s = new SAMLAttributeStatement(basicSubject, ArrayIterator(attv,1)); SAMLStatement* sv[] = { s }; SAMLAssertion* assertion = new SAMLAssertion(issuer.get(), NULL, NULL, EMPTY(SAMLCondition*), ArrayIterator(sv,1)); assertionVector.push_back(assertion); free(issuerString); free(fqanString); } //end if validity_indicator } // end for // SAMLAssertion* av[] = { assertion }; // SAMLEvidence* evidence = new SAMLEvidence(ArrayIterator(av,1)); SAMLEvidence * evidence = NULL; if(assertionVector.size()>0) { evidence = new SAMLEvidence(assertionVector); } // create query // SAMLAuthorizationDecisionQuery* query = new SAMLAuthorizationDecisionQuery(subjectWithCertChain, resource.get(), actionVector, evidence); static saml::QName respondWith1(saml::XML::SAML_NS,L(AuthorizationDecisionStatement)); static saml::QName respondWith2(saml::XML::OSG_SAML_NS,L(ObligatedAuthorizationDecisionStatement)); static saml::QName respondWiths[] = { respondWith1, respondWith2 } ; //SAMLRequest* samlRequest = new SAMLRequest(ArrayIterator(&g_respondWith,1), query); SAMLRequest* samlRequest = new SAMLRequest(ArrayIterator(respondWiths,2), query); //SAMLRequest* samlRequest = new SAMLRequest(EMPTY(saml::QName) , query); // conversion to string DOMNode * n = samlRequest->toDOM(); static XMLCh UTF8[]={ chLatin_U, chLatin_T, chLatin_F, chDigit_8, chNull }; static const XMLCh impltype[] = { chLatin_L, chLatin_S, chNull }; DOMImplementation* impl=DOMImplementationRegistry::getDOMImplementation(impltype); DOMWriter* serializer=(static_cast(impl))->createDOMWriter(); serializer->setEncoding(UTF8); try { MemBufFormatTarget target; if (!serializer->writeNode(&target,*n ) ) throw SAMLException("operator <<: unable to serialize XML instance"); unsigned int len=target.getLen(); const XMLByte* byteptr=target.getRawBuffer(); retSamlQuery = strdup( (char *) target.getRawBuffer()); } catch (...) { serializer->release(); return NULL; } serializer->release(); return retSamlQuery; } /* processResponse * takes a received XML response, parses it looking for * AuthorizationDecisionStatements and ObligatedAuthorizationDecisionStatements. * The response is compared to a provided request. * It will return 0 iff the received statement had Decision="Permit" set * and will return 1 otherwise. If the received statement had obligations * these will be parsed for opensciencegrid UserId and GroupId attributes * and the value of these attributes returned via allocated strings. The * calling application must take care of deallocating these strings * */ int processResponse(char * response, char* originalRequest, char** local_identity, char** local_group) { int samlDecision = 1; SAMLConfig& config=SAMLConfig::getConfig(); std::istringstream requestStream(originalRequest); std::istringstream responseStream(response); try { SAMLRequest reqObj(requestStream); SAMLResponse respObj(responseStream); //verify that InResponseTo matches RequestId if InResponseTo is present in response if(respObj.getInResponseTo() && (XMLString::compareString(respObj.getInResponseTo(),reqObj.getId())!=0) ) { logerr2("SAML response has InResponseTo that doesn't match request. InResponseTo: ", XMLString::transcode(respObj.getInResponseTo()) ); return 1; } //retrieve query from request so that we can later verify that //response really corresponds to the specified request, const SAMLAuthorizationDecisionQuery* query = NULL; if( !(query=dynamic_cast(reqObj.getQuery())) ) { logerr ("Unable to retrieve AuthorizationDecisionQuery from request"); return 1; } //retrieve embedded assertions Iterator assertionIterator=respObj.getAssertions(); // process assertions, we are only expecting one, if several are present // then all have to match the request and all have to be Permit // also the obligations from the later ones will override userid/groupid values // of the earlier ones while (assertionIterator.hasNext()) { SAMLAssertion* assertion = assertionIterator.next(); // check assertion validity time if present (getNotBefore getNotOnOrAfter) const XMLDateTime* notBefore = assertion->getNotBefore(); const XMLDateTime* notOnOrAfter = assertion->getNotOnOrAfter(); // get current time (plus add the defined clock skew) time_t now = time(NULL)+config.clock_skew_secs;; struct tm res; struct tm* ptime=gmtime_r(&now,&res); char timebuf[32]; strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime); auto_ptr_XMLCh timeptr(timebuf); XMLDateTime before(timeptr.get()); before.parseDateTime(); // if notBefore present if(notBefore && XMLDateTime::compareOrder(&before,notBefore)==XMLDateTime::LESS_THAN) { logerr("Skipping assertion that is not yet valid - check system clock skew "); continue; } // get current time (less defined clock skew) now=time(NULL)-config.clock_skew_secs; ptime=gmtime_r(&now,&res); strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime); auto_ptr_XMLCh timeptr2(timebuf); XMLDateTime after(timeptr2.get()); after.parseDateTime(); if (notOnOrAfter && XMLDateTime::compareOrder(notOnOrAfter,&after)!=XMLDateTime::GREATER_THAN) { logerr ("Skipping assertion that has expired"); continue; } // ok, now retrieve all statements embedded in the assertion Iterator statementIterator=assertion->getStatements(); // and process each of these statements while(statementIterator.hasNext()) { SAMLStatement* s = statementIterator.next(); ObligatedAuthorizationDecisionStatement* ostmt = NULL; SAMLAuthorizationDecisionStatement* stmt = NULL; // is it an ObligatedAuthorizationDecisionStatement? if(ostmt= dynamic_cast (s) ) { logit("received ObligatedAuthorizationDecisionStatement"); // parse the decision statement if(ostmt==NULL) { // cast failed logerr("unable to process ObligatedAuthorizationDecisionStatement - skipping statement"); continue; } // verify that this decision statement matches our request // else we can skip it // first check the subject if(XMLString::compareString(ostmt->getSubject()->getNameIdentifier()->getName(), query->getSubject()->getNameIdentifier()->getName()) != 0) { logerr("ObligatedAuthorizationDecisionStatement doesn't match request subject - skipping statement"); continue; } logit2("ObligatedAuthorizationDecisionStatement - Decision is ", XMLString::transcode(ostmt->getDecision())); // set samlDecision to later return 0 and continue processing only if it is a permit? if(!XMLString::compareString(SAMLDecision::Permit, ostmt->getDecision()) ) samlDecision = 0; // if it isn't a permit then we can stop processing here and return 1 // for deny/indeterminate else return 1; // now verify that the requested action is present (and thus allowed) Iterator reqActionIterator=query->getActions(); Iterator respActionIterator=ostmt->getActions(); int actionFound = 0; // find each requested action in the response while(reqActionIterator.hasNext()) { SAMLAction* reqAction = reqActionIterator.next(); SAMLAction* respAction = respActionIterator.next(); actionFound = 0; do { if(respAction && !XMLString::compareString(reqAction->getData(), respAction->getData()) ) { actionFound = 1; break; // matching action found } } while (respAction = respActionIterator.next()); if (actionFound != 1) break; // bail out, we are missing an action respActionIterator.reset(); // start from the beginning in the response actions for next iteration } if (actionFound !=1) { logerr("unable to locate requested action in response - ignoring decision statement"); continue; } Iterator obligationIterator=ostmt->getObligations(); auto_ptr_XMLCh userIdObligation("opensciencegrid:authorization:UserIdObligation"); auto_ptr_XMLCh groupIdObligation("opensciencegrid:authorization:GroupIdObligation"); while(obligationIterator.hasNext()) { // need to check what kind of obligation it is, based on that set groupid/userid // if not understood --> failure char * temp_val; XACMLObligation* obligation = (XACMLObligation*) obligationIterator.next(); if(!XMLString::compareString(obligation->getObligationId(), userIdObligation.get())) { temp_val = XMLString::transcode(obligation->getValue()); logit2("Parsing UserId obligation with value: ", temp_val); *local_identity=(char*)malloc(strlen(temp_val)+1); if(*local_identity) { strcpy(*local_identity, temp_val); } else { logerr("Error allocating memory for local_identity"); return 1; } } else if(!XMLString::compareString(obligation->getObligationId(), groupIdObligation.get())) { temp_val = XMLString::transcode(obligation->getValue()); logit2("Parsing GroupId obligation with value: ", temp_val); *local_group=(char*)malloc(strlen(temp_val)+1); if(*local_group) { strcpy(*local_group, temp_val); } else { logerr("Error allocating memory for local_group"); return 1; } } else { logerr2("Unsupported Obligation found. Type: ", XMLString::transcode(obligation->getObligationId())); return 1; } } // end while obligationIterator } // end if type = ObligatedAuthorizationDecisionStatement else if(stmt= dynamic_cast (s) ) { logit("received AuthorizationDecisionStatement"); SAMLAuthorizationDecisionStatement* stmt = dynamic_cast (s); if(stmt==NULL) { // cast failed logerr("unable to process this AuthorizationDecisionStatement - skipping statement"); continue; } logit2("Parsed statement - Decision is: ", XMLString::transcode(stmt->getDecision())); if(!XMLString::compareString(SAMLDecision::Permit, stmt->getDecision()) ) samlDecision = 0; Iterator actionIterator=stmt->getActions(); while(actionIterator.hasNext()) { SAMLAction* action = (SAMLAction*) actionIterator.next(); logit2("Found action: ", XMLString::transcode(action->getData()) ); } } // end if SAMLAUthorizationDecisionStatement else logerr ("received SAML Statement that is not supported!"); } // end while statementIterator } //end while assertionIterator.hasNext() } catch (MalformedException& e) { logerr2("Unable to process received decision statement: ", e.what()); return 1; } catch (SAMLException& e) { logerr2("Did not receive a SAML Assertion in the response. Message: ", e.what()); return 1; } catch (...) { logerr("unknown exception - could not parse decision statement "); return 1; } return samlDecision; }