--- /dev/null
+/Makefile.in
+/Makefile
+/.libs
+/.deps
+/*.lo
+/*.la
+/Debug
+/*.plg
+/Release
+/*.dep
+/*.mak
+/*.aps
\ No newline at end of file
--- /dev/null
+## $Id$
+
+AUTOMAKE_OPTIONS = foreign
+
+plugindir = $(libexecdir)
+plugin_LTLIBRARIES = adfs.la
+
+noinst_HEADERS = internal.h
+
+adfs_la_LIBADD = \
+ ../shib/libshib.la \
+ ../shib-target/libshib-target.la
+
+adfs_la_SOURCES = \
+ adfs.cpp \
+ listener.cpp \
+ handlers.cpp \
+ XML.cpp
+
+
+adfs_la_LDFLAGS = -module -avoid-version
+
+install-exec-hook:
+ for la in $(plugin_LTLIBRARIES) ; do rm -f $(DESTDIR)$(plugindir)/$$la ; done
+
+EXTRA_DIST = adfs.dsp resource.h adfs.rc
--- /dev/null
+/*
+ * Copyright 2001-2005 Internet2
+ *
+ * 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.
+ */
+
+/* XML.cpp - XML constants
+
+ Scott Cantor
+ 6/4/02
+
+ $History:$
+*/
+
+#include "internal.h"
+
+// Namespace and schema string literals
+
+const XMLCh adfs::XML::WSFED_NS[] = // http://schemas.xmlsoap.org/ws/2003/07/secext
+{ chLatin_h, chLatin_t, chLatin_t, chLatin_p, chColon, chForwardSlash, chForwardSlash,
+ chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a, chLatin_s, chPeriod,
+ chLatin_x, chLatin_m, chLatin_l, chLatin_s, chLatin_o, chLatin_a, chLatin_p, chPeriod,
+ chLatin_o, chLatin_r, chLatin_g, chForwardSlash, chLatin_w, chLatin_s, chForwardSlash,
+ chDigit_2, chDigit_0, chDigit_0, chDigit_3, chForwardSlash, chDigit_0, chDigit_7, chForwardSlash,
+ chLatin_s, chLatin_e, chLatin_c, chLatin_e, chLatin_x, chLatin_t, chNull
+};
+
+const XMLCh adfs::XML::WSTRUST_NS[] = // http://schemas.xmlsoap.org/ws/2005/02/trust
+{ chLatin_h, chLatin_t, chLatin_t, chLatin_p, chColon, chForwardSlash, chForwardSlash,
+ chLatin_s, chLatin_c, chLatin_h, chLatin_e, chLatin_m, chLatin_a, chLatin_s, chPeriod,
+ chLatin_x, chLatin_m, chLatin_l, chLatin_s, chLatin_o, chLatin_a, chLatin_p, chPeriod,
+ chLatin_o, chLatin_r, chLatin_g, chForwardSlash, chLatin_w, chLatin_s, chForwardSlash,
+ chDigit_2, chDigit_0, chDigit_0, chDigit_5, chForwardSlash, chDigit_0, chDigit_2, chForwardSlash,
+ chLatin_t, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull
+};
+
+const XMLCh adfs::XML::WSTRUST_SCHEMA_ID[] =
+{ chLatin_W, chLatin_S, chDash, chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chPeriod, chLatin_x, chLatin_s, chLatin_d, chNull
+};
+
+const XMLCh adfs::XML::Literals::RequestedSecurityToken[] =
+{ chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t, chLatin_e, chLatin_d,
+ chLatin_S, chLatin_e, chLatin_c, chLatin_u, chLatin_r, chLatin_i, chLatin_t, chLatin_y,
+ chLatin_T, chLatin_o, chLatin_k, chLatin_e, chLatin_n, chNull
+};
+
+const XMLCh adfs::XML::Literals::RequestSecurityTokenResponse[] =
+{ chLatin_R, chLatin_e, chLatin_q, chLatin_u, chLatin_e, chLatin_s, chLatin_t,
+ chLatin_S, chLatin_e, chLatin_c, chLatin_u, chLatin_r, chLatin_i, chLatin_t, chLatin_y,
+ chLatin_T, chLatin_o, chLatin_k, chLatin_e, chLatin_n,
+ chLatin_R, chLatin_e, chLatin_s, chLatin_p, chLatin_o, chLatin_n, chLatin_s, chLatin_e, chNull
+};
--- /dev/null
+/*
+ * Copyright 2001-2005 Internet2
+ *
+ * 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.
+ */
+
+/* adfs.cpp - bootstraps the ADFS extension library
+
+ Scott Cantor
+ 10/10/05
+*/
+
+#ifdef WIN32
+# define ADFS_EXPORTS __declspec(dllexport)
+#else
+# define ADFS_EXPORTS
+#endif
+
+#include "internal.h"
+
+#include <xercesc/util/Base64.hpp>
+
+
+using namespace std;
+using namespace saml;
+using namespace shibboleth;
+using namespace adfs;
+using namespace log4cpp;
+
+// Plugin Factories
+PlugManager::Factory ADFSListenerFactory;
+PlugManager::Factory ADFSSessionInitiatorFactory;
+PlugManager::Factory ADFSHandlerFactory;
+
+
+extern "C" int ADFS_EXPORTS saml_extension_init(void*)
+{
+ // Register extension schema.
+ saml::XML::registerSchema(adfs::XML::WSTRUST_NS,adfs::XML::WSTRUST_SCHEMA_ID);
+
+ // Register plugin factories (some override existing Shib functionality).
+ SAMLConfig& conf=SAMLConfig::getConfig();
+ conf.getPlugMgr().regFactory(shibtarget::XML::MemoryListenerType,&ADFSListenerFactory);
+
+ auto_ptr_char temp1(Constants::SHIB_SESSIONINIT_PROFILE_URI);
+ conf.getPlugMgr().regFactory(temp1.get(),&ADFSSessionInitiatorFactory);
+
+ auto_ptr_char temp2(adfs::XML::WSFED_NS);
+ conf.getPlugMgr().regFactory(temp2.get(),&ADFSHandlerFactory);
+
+ return 0;
+}
+
+extern "C" void ADFS_EXPORTS saml_extension_term()
+{
+ // Unregister metadata factories
+ SAMLConfig& conf=SAMLConfig::getConfig();
+ conf.getPlugMgr().unregFactory(shibtarget::XML::MemoryListenerType);
+
+ auto_ptr_char temp1(Constants::SHIB_SESSIONINIT_PROFILE_URI);
+ conf.getPlugMgr().unregFactory(temp1.get());
+
+ auto_ptr_char temp2(adfs::XML::WSFED_NS);
+ conf.getPlugMgr().unregFactory(temp2.get());
+}
+
+// For now, we'll just put the meat of the profile here.
+
+SAMLAuthenticationStatement* adfs::checkAssertionProfile(const SAMLAssertion* a)
+{
+ // Is it signed?
+ if (!a->isSigned())
+ throw FatalProfileException("rejected unsigned ADFS assertion");
+
+ // Is it valid?
+ time_t now=time(NULL);
+ SAMLConfig& config=SAMLConfig::getConfig();
+ if (a->getIssueInstant()->getEpoch() < now-(2*config.clock_skew_secs))
+ throw ExpiredAssertionException("rejected expired ADFS assertion");
+
+ const SAMLDateTime* notBefore=a->getNotBefore();
+ const SAMLDateTime* notOnOrAfter=a->getNotOnOrAfter();
+ if (!notBefore || !notOnOrAfter)
+ throw ExpiredAssertionException("rejected ADFS assertion without time conditions");
+ if (now+config.clock_skew_secs < notBefore->getEpoch())
+ throw ExpiredAssertionException("rejected ADFS assertion that is not yet valid");
+ if (notOnOrAfter->getEpoch() <= now-config.clock_skew_secs)
+ throw ExpiredAssertionException("rejected expired ADFS assertion");
+
+ // Look for an authentication statement.
+ SAMLAuthenticationStatement* as=NULL;
+ for (Iterator<SAMLStatement*> statements=a->getStatements(); !as && statements.hasNext();)
+ as=dynamic_cast<SAMLAuthenticationStatement*>(statements.next());
+ if (!as)
+ throw FatalProfileException("rejecting ADFS assertion without authentication statement");
+
+ return as;
+}
+
+/*************************************************************************
+ * CGI Parser implementation
+ */
+
+CgiParse::CgiParse(const char* data, unsigned int len)
+{
+ const char* pch = data;
+ unsigned int cl = len;
+
+ while (cl && pch) {
+ char *name;
+ char *value;
+ value=fmakeword('&',&cl,&pch);
+ plustospace(value);
+ url_decode(value);
+ name=makeword(value,'=');
+ kvp_map[name]=value;
+ free(name);
+ }
+}
+
+CgiParse::~CgiParse()
+{
+ for (map<string,char*>::iterator i=kvp_map.begin(); i!=kvp_map.end(); i++)
+ free(i->second);
+}
+
+const char*
+CgiParse::get_value(const char* name) const
+{
+ map<string,char*>::const_iterator i=kvp_map.find(name);
+ if (i==kvp_map.end())
+ return NULL;
+ return i->second;
+}
+
+/* Parsing routines modified from NCSA source. */
+char *
+CgiParse::makeword(char *line, char stop)
+{
+ int x = 0,y;
+ char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
+
+ for(x=0;((line[x]) && (line[x] != stop));x++)
+ word[x] = line[x];
+
+ word[x] = '\0';
+ if(line[x])
+ ++x;
+ y=0;
+
+ while(line[x])
+ line[y++] = line[x++];
+ line[y] = '\0';
+ return word;
+}
+
+char *
+CgiParse::fmakeword(char stop, unsigned int *cl, const char** ppch)
+{
+ int wsize;
+ char *word;
+ int ll;
+
+ wsize = 1024;
+ ll=0;
+ word = (char *) malloc(sizeof(char) * (wsize + 1));
+
+ while(1)
+ {
+ word[ll] = *((*ppch)++);
+ if(ll==wsize-1)
+ {
+ word[ll+1] = '\0';
+ wsize+=1024;
+ word = (char *)realloc(word,sizeof(char)*(wsize+1));
+ }
+ --(*cl);
+ if((word[ll] == stop) || word[ll] == EOF || (!(*cl)))
+ {
+ if(word[ll] != stop)
+ ll++;
+ word[ll] = '\0';
+ return word;
+ }
+ ++ll;
+ }
+}
+
+void
+CgiParse::plustospace(char *str)
+{
+ register int x;
+
+ for(x=0;str[x];x++)
+ if(str[x] == '+') str[x] = ' ';
+}
+
+char
+CgiParse::x2c(char *what)
+{
+ register char digit;
+
+ digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
+ digit *= 16;
+ digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
+ return(digit);
+}
+
+void
+CgiParse::url_decode(char *url)
+{
+ register int x,y;
+
+ for(x=0,y=0;url[y];++x,++y)
+ {
+ if((url[x] = url[y]) == '%')
+ {
+ url[x] = x2c(&url[y+1]);
+ y+=2;
+ }
+ }
+ url[x] = '\0';
+}
+
+static inline char hexchar(unsigned short s)
+{
+ return (s<=9) ? ('0' + s) : ('A' + s - 10);
+}
+
+string CgiParse::url_encode(const char* s)
+{
+ static char badchars[]="\"\\+<>#%{}|^~[]`;/?:@=&";
+
+ string ret;
+ for (; *s; s++) {
+ if (strchr(badchars,*s) || *s<=0x20 || *s>=0x7F) {
+ ret+='%';
+ ret+=hexchar(*s >> 4);
+ ret+=hexchar(*s & 0x0F);
+ }
+ else
+ ret+=*s;
+ }
+ return ret;
+}
+
+// CDC implementation
+
+const char CommonDomainCookie::CDCName[] = "_saml_idp";
+
+CommonDomainCookie::CommonDomainCookie(const char* cookie)
+{
+ if (!cookie)
+ return;
+
+ Category& log=Category::getInstance(ADFS_LOGCAT".CommonDomainCookie");
+
+ // Copy it so we can URL-decode it.
+ char* b64=strdup(cookie);
+ CgiParse::url_decode(b64);
+
+ // Chop it up and save off elements.
+ vector<string> templist;
+ char* ptr=b64;
+ while (*ptr) {
+ while (*ptr && isspace(*ptr)) ptr++;
+ char* end=ptr;
+ while (*end && !isspace(*end)) end++;
+ templist.push_back(string(ptr,end-ptr));
+ ptr=end;
+ }
+ free(b64);
+
+ // Now Base64 decode the list.
+ for (vector<string>::iterator i=templist.begin(); i!=templist.end(); i++) {
+ unsigned int len;
+ XMLByte* decoded=Base64::decode(reinterpret_cast<const XMLByte*>(i->c_str()),&len);
+ if (decoded && *decoded) {
+ m_list.push_back(reinterpret_cast<char*>(decoded));
+ XMLString::release(&decoded);
+ }
+ else
+ log.warn("cookie element does not appear to be base64-encoded");
+ }
+}
+
+const char* CommonDomainCookie::set(const char* providerId)
+{
+ // First scan the list for this IdP.
+ for (vector<string>::iterator i=m_list.begin(); i!=m_list.end(); i++) {
+ if (*i == providerId) {
+ m_list.erase(i);
+ break;
+ }
+ }
+
+ // Append it to the end.
+ m_list.push_back(providerId);
+
+ // Now rebuild the delimited list.
+ string delimited;
+ for (vector<string>::const_iterator j=m_list.begin(); j!=m_list.end(); j++) {
+ if (!delimited.empty()) delimited += ' ';
+
+ unsigned int len;
+ XMLByte* b64=Base64::encode(reinterpret_cast<const XMLByte*>(j->c_str()),j->length(),&len);
+ XMLByte *pos, *pos2;
+ for (pos=b64, pos2=b64; *pos2; pos2++)
+ if (isgraph(*pos2))
+ *pos++=*pos2;
+ *pos=0;
+
+ delimited += reinterpret_cast<char*>(b64);
+ XMLString::release(&b64);
+ }
+
+ m_encoded=CgiParse::url_encode(delimited.c_str());
+ return m_encoded.c_str();
+}
--- /dev/null
+# Microsoft Developer Studio Project File - Name="adfs" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=adfs - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "adfs.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "adfs.mak" CFG="adfs - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "adfs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "adfs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "adfs - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "ADFS_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GR /GX /O2 /I ".." /I "..\..\..\opensaml\c" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 log4cpp.lib xerces-c_2.lib saml_5.lib /nologo /dll /machine:I386 /out:"Release/adfs.so" /libpath:"..\..\..\opensaml\c\saml\Release"
+
+!ELSEIF "$(CFG)" == "adfs - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "ADFS_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GR /GX /ZI /Od /I ".." /I "..\..\..\opensaml\c" /D "_WINDOWS" /D "_WINDLL" /D "WIN32" /D "_DEBUG" /D "_MBCS" /FR /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 log4cppD.lib xerces-c_2D.lib saml_5D.lib /nologo /dll /debug /machine:I386 /out:"Debug/adfs.so" /pdbtype:sept /libpath:"..\..\..\opensaml\c\saml\Debug"
+
+!ENDIF
+
+# Begin Target
+
+# Name "adfs - Win32 Release"
+# Name "adfs - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\adfs.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\adfs.rc
+# End Source File
+# Begin Source File
+
+SOURCE=.\handlers.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\internal.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\listener.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\resource.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\XML.cpp
+# End Source File
+# End Target
+# End Project
--- /dev/null
+//Microsoft Developer Studio generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE DISCARDABLE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+#ifndef _MAC
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,3,0,0
+ PRODUCTVERSION 1,3,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "Comments", "\0"
+ VALUE "CompanyName", "Internet2\0"
+ VALUE "FileDescription", "Shibboleth ADFS Plugins\0"
+ VALUE "FileVersion", "1, 3, 0, 0\0"
+ VALUE "InternalName", "adfs\0"
+ VALUE "LegalCopyright", "Copyright © 2005 Internet2\0"
+ VALUE "LegalTrademarks", "\0"
+ VALUE "OriginalFilename", "adfs.so\0"
+ VALUE "PrivateBuild", "\0"
+ VALUE "ProductName", "Shibboleth 1.3\0"
+ VALUE "ProductVersion", "1, 3, 0, 0\0"
+ VALUE "SpecialBuild", "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+#endif // !_MAC
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
--- /dev/null
+/*
+ * Copyright 2001-2005 Internet2
+ *
+ * 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.
+ */
+
+/*
+ * handlers.cpp -- ADFS-aware profile handlers that plug into SP
+ *
+ * Scott Cantor
+ * 10/10/2005
+ */
+
+#include "internal.h"
+
+#ifndef HAVE_STRCASECMP
+# define strcasecmp stricmp
+#endif
+
+using namespace std;
+using namespace saml;
+using namespace shibboleth;
+using namespace shibtarget;
+using namespace adfs;
+using namespace log4cpp;
+
+namespace {
+
+ // TODO: Refactor/extend API so I don't have to cut/paste this code out of libshib-target
+ class SessionInitiator : virtual public IHandler
+ {
+ public:
+ SessionInitiator(const DOMElement* e) {}
+ ~SessionInitiator() {}
+ pair<bool,void*> run(ShibTarget* st, const IPropertySet* handler, bool isHandler=true);
+
+ private:
+ const IPropertySet* getCompatibleACS(const IApplication* app, const vector<ShibProfile>& profiles);
+ pair<bool,void*> ShibAuthnRequest(
+ ShibTarget* st,
+ const IPropertySet* shire,
+ const char* dest,
+ const char* target,
+ const char* providerId
+ );
+ pair<bool,void*> ADFSAuthnRequest(
+ ShibTarget* st,
+ const IPropertySet* shire,
+ const char* dest,
+ const char* target,
+ const char* providerId
+ );
+ };
+
+ class ADFSHandler : virtual public IHandler
+ {
+ public:
+ ADFSHandler(const DOMElement* e) {}
+ ~ADFSHandler() {}
+ pair<bool,void*> run(ShibTarget* st, const IPropertySet* handler, bool isHandler=true);
+ };
+}
+
+
+IPlugIn* ADFSSessionInitiatorFactory(const DOMElement* e)
+{
+ return new SessionInitiator(e);
+}
+
+IPlugIn* ADFSHandlerFactory(const DOMElement* e)
+{
+ return new ADFSHandler(e);
+}
+
+pair<bool,void*> SessionInitiator::run(ShibTarget* st, const IPropertySet* handler, bool isHandler)
+{
+ string dupresource;
+ const char* resource=NULL;
+ const IPropertySet* ACS=NULL;
+ const IApplication* app=st->getApplication();
+
+ if (isHandler) {
+ /*
+ * Binding is CGI query string with:
+ * target the resource to direct back to later
+ * acsIndex optional index of an ACS to use on the way back in
+ * providerId optional direct invocation of a specific IdP
+ */
+ string query=st->getArgs();
+ CgiParse parser(query.c_str(),query.length());
+
+ const char* option=parser.get_value("acsIndex");
+ if (option)
+ ACS=app->getAssertionConsumerServiceByIndex(atoi(option));
+ option=parser.get_value("providerId");
+
+ resource=parser.get_value("target");
+ if (!resource || !*resource) {
+ pair<bool,const char*> home=app->getString("homeURL");
+ if (home.first)
+ resource=home.second;
+ else
+ throw FatalProfileException("Session initiator requires a target parameter or a homeURL application property.");
+ }
+ else if (!option) {
+ dupresource=resource;
+ resource=dupresource.c_str();
+ }
+
+ if (option) {
+ // Here we actually use metadata to invoke the SSO service directly.
+ Metadata m(app->getMetadataProviders());
+ const IEntityDescriptor* entity=m.lookup(option);
+ if (!entity)
+ throw MetadataException("Session initiator unable to locate metadata for provider ($1).", params(1,option));
+
+ // Look for an IdP role with Shib support.
+ const IIDPSSODescriptor* role=entity->getIDPSSODescriptor(Constants::SHIB_NS);
+ if (role) {
+ // Look for a SSO endpoint with Shib support.
+ const IEndpointManager* SSO=role->getSingleSignOnServiceManager();
+ const IEndpoint* ep=SSO->getEndpointByBinding(Constants::SHIB_AUTHNREQUEST_PROFILE_URI);
+ if (ep) {
+ if (!ACS) {
+ // Look for an ACS with SAML support.
+ vector<ShibProfile> v;
+ v.push_back(SAML11_POST);
+ v.push_back(SAML11_ARTIFACT);
+ v.push_back(SAML10_ARTIFACT);
+ v.push_back(SAML10_POST);
+ ACS=getCompatibleACS(app,v);
+ }
+ auto_ptr_char dest(ep->getLocation());
+ return ShibAuthnRequest(
+ st,ACS ? ACS : app->getDefaultAssertionConsumerService(),dest.get(),resource,app->getString("providerId").second
+ );
+ }
+ }
+ // Look for an IdP role with ADFS support.
+ role=entity->getIDPSSODescriptor(adfs::XML::WSFED_NS);
+ if (role) {
+ // Finally, look for a SSO endpoint with ADFS support.
+ const IEndpointManager* SSO=role->getSingleSignOnServiceManager();
+ const IEndpoint* ep=SSO->getEndpointByBinding(adfs::XML::WSFED_NS);
+ if (ep) {
+ if (!ACS) {
+ // Look for an ACS with ADFS support.
+ vector<ShibProfile> v;
+ v.push_back(ADFS_SSO);
+ ACS=getCompatibleACS(app,v);
+ }
+ auto_ptr_char dest(ep->getLocation());
+ return ADFSAuthnRequest(
+ st,ACS ? ACS : app->getDefaultAssertionConsumerService(),dest.get(),resource,app->getString("providerId").second
+ );
+ }
+ }
+
+ throw MetadataException(
+ "Session initiator unable to locate a compatible identity provider SSO endpoint for provider ($1).",
+ params(1,option)
+ );
+ }
+ }
+ else {
+ // We're running as a "virtual handler" from within the filter.
+ // The target resource is the current one and everything else is defaulted.
+ resource=st->getRequestURL();
+ }
+
+ // For now, we only support external session initiation via a wayfURL
+ pair<bool,const char*> wayfURL=handler->getString("wayfURL");
+ if (!wayfURL.first)
+ throw ConfigurationException("Session initiator is missing wayfURL property.");
+
+ pair<bool,const XMLCh*> wayfBinding=handler->getXMLString("wayfBinding");
+ if (!wayfBinding.first || !XMLString::compareString(wayfBinding.second,Constants::SHIB_AUTHNREQUEST_PROFILE_URI))
+ // Standard Shib 1.x
+ return ShibAuthnRequest(st,ACS,wayfURL.second,resource,app->getString("providerId").second);
+ else if (!XMLString::compareString(wayfBinding.second,Constants::SHIB_LEGACY_AUTHNREQUEST_PROFILE_URI))
+ // Shib pre-1.2
+ return ShibAuthnRequest(st,ACS,wayfURL.second,resource,NULL);
+ else if (!strcmp(handler->getString("wayfBinding").second,"urn:mace:shibboleth:1.0:profiles:EAuth")) {
+ pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+ if (!localRelayState.first || !localRelayState.second)
+ throw ConfigurationException("E-Authn requests cannot include relay state, so localRelayState must be enabled.");
+
+ // Here we store the state in a cookie.
+ pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
+ st->setCookie(shib_cookie.first,CgiParse::url_encode(resource) + shib_cookie.second);
+ return make_pair(true, st->sendRedirect(wayfURL.second));
+ }
+ else if (!XMLString::compareString(wayfBinding.second,adfs::XML::WSFED_NS))
+ return ADFSAuthnRequest(st,ACS,wayfURL.second,resource,app->getString("providerId").second);
+
+ throw UnsupportedProfileException("Unsupported WAYF binding ($1).", params(1,handler->getString("wayfBinding").second));
+}
+
+// Get an ACS that can handle one of the desired profiles
+const IPropertySet* SessionInitiator::getCompatibleACS(const IApplication* app, const vector<ShibProfile>& profiles)
+{
+ // This isn't going to be very efficient until I can revise the IApplication API to
+ // support ACS lookup by profile.
+
+ int mask=0;
+ for (vector<ShibProfile>::const_iterator p=profiles.begin(); p!=profiles.end(); p++)
+ mask+=*p;
+
+ // See if the default is acceptable.
+ const IPropertySet* ACS=app->getDefaultAssertionConsumerService();
+ pair<bool,const XMLCh*> binding=ACS ? ACS->getXMLString("Binding") : pair<bool,const XMLCh*>(false,NULL);
+ if (!ACS || !binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)) {
+ pair<bool,unsigned int> version =
+ ACS ? ACS->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol") : pair<bool,unsigned int>(false,1);
+ if (!version.first)
+ version.second=1;
+ if (mask & (version.second==1 ? SAML11_POST : SAML10_POST))
+ return ACS;
+ }
+ else if (!XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT)) {
+ pair<bool,unsigned int> version=ACS->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol");
+ if (!version.first)
+ version.second=1;
+ if (mask & (version.second==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT))
+ return ACS;
+ }
+ else if (!XMLString::compareString(binding.second,adfs::XML::WSFED_NS)) {
+ if (mask & ADFS_SSO)
+ return ACS;
+ }
+
+ // If not, iterate by profile.
+ for (vector<ShibProfile>::const_iterator i=profiles.begin(); i!=profiles.end(); i++) {
+ for (unsigned short j=0; j<=65535; j++) {
+ ACS=app->getAssertionConsumerServiceByIndex(j);
+ if (!ACS && j)
+ break; // we're past 0 and didn't get a hit, so we'll bail
+ else if (ACS) {
+ binding=ACS->getXMLString("Binding");
+ pair<bool,unsigned int> version=ACS->getUnsignedInt("MinorVersion","urn:oasis:names:tc:SAML:1.0:protocol");
+ if (!version.first)
+ version.second=1;
+ switch (*i) {
+ case SAML11_POST:
+ if (version.second==1 && (!binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)))
+ return ACS;
+ break;
+ case SAML11_ARTIFACT:
+ if (version.second==1 && !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT))
+ return ACS;
+ break;
+ case ADFS_SSO:
+ if (!XMLString::compareString(binding.second,adfs::XML::WSFED_NS))
+ return ACS;
+ break;
+ case SAML10_POST:
+ if (version.second==0 && (!binding.first || !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_POST)))
+ return ACS;
+ break;
+ case SAML10_ARTIFACT:
+ if (version.second==0 && !XMLString::compareString(binding.second,SAMLBrowserProfile::BROWSER_ARTIFACT))
+ return ACS;
+ break;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+// Handles Shib 1.x AuthnRequest profile.
+pair<bool,void*> SessionInitiator::ShibAuthnRequest(
+ ShibTarget* st,
+ const IPropertySet* shire,
+ const char* dest,
+ const char* target,
+ const char* providerId
+ )
+{
+ if (!shire) {
+ // Look for an ACS with SAML support.
+ vector<ShibProfile> v;
+ v.push_back(SAML11_POST);
+ v.push_back(SAML11_ARTIFACT);
+ v.push_back(SAML10_ARTIFACT);
+ v.push_back(SAML10_POST);
+ shire=getCompatibleACS(st->getApplication(),v);
+ }
+ if (!shire)
+ shire=st->getApplication()->getDefaultAssertionConsumerService();
+
+ // Compute the ACS URL. We add the ACS location to the handler baseURL.
+ // Legacy configs will not have an ACS specified, so no suffix will be added.
+ string ACSloc=st->getHandlerURL(target);
+ if (shire) ACSloc+=shire->getString("Location").second;
+
+ char timebuf[16];
+ sprintf(timebuf,"%u",time(NULL));
+ string req=string(dest) + "?shire=" + CgiParse::url_encode(ACSloc.c_str()) + "&time=" + timebuf;
+
+ // How should the resource value be preserved?
+ pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+ if (!localRelayState.first || !localRelayState.second) {
+ // The old way, just send it along.
+ req+="&target=" + CgiParse::url_encode(target);
+ }
+ else {
+ // Here we store the state in a cookie and send a fixed
+ // value to the IdP so we can recognize it on the way back.
+ pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
+ st->setCookie(shib_cookie.first,CgiParse::url_encode(target) + shib_cookie.second);
+ req+="&target=cookie";
+ }
+
+ // Only omitted for 1.1 style requests.
+ if (providerId)
+ req+="&providerId=" + CgiParse::url_encode(providerId);
+
+ return make_pair(true, st->sendRedirect(req));
+}
+
+// Handles ADFS token request profile.
+pair<bool,void*> SessionInitiator::ADFSAuthnRequest(
+ ShibTarget* st,
+ const IPropertySet* shire,
+ const char* dest,
+ const char* target,
+ const char* providerId
+ )
+{
+ if (!shire) {
+ // Look for an ACS with ADFS support.
+ vector<ShibProfile> v;
+ v.push_back(ADFS_SSO);
+ shire=getCompatibleACS(st->getApplication(),v);
+ }
+ if (!shire)
+ shire=st->getApplication()->getDefaultAssertionConsumerService();
+
+ // Compute the ACS URL. We add the ACS location to the handler baseURL.
+ // Legacy configs will not have an ACS specified, so no suffix will be added.
+ string ACSloc=st->getHandlerURL(target);
+ if (shire) ACSloc+=shire->getString("Location").second;
+
+ // UTC timestamp
+#ifndef HAVE_GMTIME_R
+ time_t epoch=time(NULL);
+ struct tm* ptime=gmtime(&epoch);
+#else
+ struct tm res;
+ struct tm* ptime=gmtime_r(&epoch,&res);
+#endif
+ char timebuf[32];
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+
+ string req=string(dest) + "?wa=wsignin1.0&wreply=" + CgiParse::url_encode(ACSloc.c_str()) + "&wct=" + CgiParse::url_encode(timebuf);
+
+ // How should the resource value be preserved?
+ pair<bool,bool> localRelayState=st->getConfig()->getPropertySet("Local")->getBool("localRelayState");
+ if (!localRelayState.first || !localRelayState.second) {
+ // The old way, just send it along.
+ req+="&wctx=" + CgiParse::url_encode(target);
+ }
+ else {
+ // Here we store the state in a cookie and send a fixed
+ // value to the IdP so we can recognize it on the way back.
+ pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibstate_");
+ st->setCookie(shib_cookie.first,CgiParse::url_encode(target) + shib_cookie.second);
+ req+="&wctx=cookie";
+ }
+
+ req+="&wtrealm=" + CgiParse::url_encode(providerId);
+
+ return make_pair(true, st->sendRedirect(req));
+}
+
+pair<bool,void*> ADFSHandler::run(ShibTarget* st, const IPropertySet* handler, bool isHandler)
+{
+ const IApplication* app=st->getApplication();
+
+ if (strcasecmp(st->getRequestMethod(), "POST"))
+ throw FatalProfileException(
+ "ADFS protocol handler does not support HTTP method ($1).", params(1,st->getRequestMethod())
+ );
+
+ if (!st->getContentType() || strcasecmp(st->getContentType(),"application/x-www-form-urlencoded"))
+ throw FatalProfileException(
+ "Blocked invalid content-type ($1) submitted to ADFS protocol handler.", params(1,st->getContentType())
+ );
+
+ string input=st->getPostData();
+ if (input.empty())
+ throw FatalProfileException("ADFS protocol handler received no data from browser.");
+
+ ShibProfile profile=ADFS_SSO;
+ string cookie,target,providerId;
+
+ string hURL=st->getHandlerURL(st->getRequestURL());
+ pair<bool,const char*> loc=handler->getString("Location");
+ string recipient=loc.first ? hURL + loc.second : hURL;
+ st->getConfig()->getListener()->sessionNew(
+ app,
+ profile,
+ recipient.c_str(),
+ input.c_str(),
+ st->getRemoteAddr(),
+ target,
+ cookie,
+ providerId
+ );
+
+ st->log(ShibTarget::LogLevelDebug, string("profile processing succeeded, new session created (") + cookie + ")");
+
+ if (target=="default") {
+ pair<bool,const char*> homeURL=app->getString("homeURL");
+ target=homeURL.first ? homeURL.second : "/";
+ }
+ else if (target=="cookie" || target.empty()) {
+ // Pull the target value from the "relay state" cookie.
+ pair<string,const char*> relay_cookie = st->getCookieNameProps("_shibstate_");
+ const char* relay_state = st->getCookie(relay_cookie.first);
+ if (!relay_state || !*relay_state) {
+ // No apparent relay state value to use, so fall back on the default.
+ pair<bool,const char*> homeURL=app->getString("homeURL");
+ target=homeURL.first ? homeURL.second : "/";
+ }
+ else {
+ char* rscopy=strdup(relay_state);
+ CgiParse::url_decode(rscopy);
+ target=rscopy;
+ free(rscopy);
+ }
+ }
+
+ // We've got a good session, set the session cookie.
+ pair<string,const char*> shib_cookie=st->getCookieNameProps("_shibsession_");
+ st->setCookie(shib_cookie.first, cookie + shib_cookie.second);
+
+ const IPropertySet* sessionProps=app->getPropertySet("Sessions");
+ pair<bool,bool> idpHistory=sessionProps->getBool("idpHistory");
+ if (!idpHistory.first || idpHistory.second) {
+ // Set an IdP history cookie locally (essentially just a CDC).
+ CommonDomainCookie cdc(st->getCookie(CommonDomainCookie::CDCName));
+
+ // Either leave in memory or set an expiration.
+ pair<bool,unsigned int> days=sessionProps->getUnsignedInt("idpHistoryDays");
+ if (!days.first || days.second==0)
+ st->setCookie(CommonDomainCookie::CDCName,string(cdc.set(providerId.c_str())) + shib_cookie.second);
+ else {
+ time_t now=time(NULL) + (days.second * 24 * 60 * 60);
+#ifdef HAVE_GMTIME_R
+ struct tm res;
+ struct tm* ptime=gmtime_r(&now,&res);
+#else
+ struct tm* ptime=gmtime(&now);
+#endif
+ char timebuf[64];
+ strftime(timebuf,64,"%a, %d %b %Y %H:%M:%S GMT",ptime);
+ st->setCookie(
+ CommonDomainCookie::CDCName,
+ string(cdc.set(providerId.c_str())) + shib_cookie.second + "; expires=" + timebuf
+ );
+ }
+ }
+
+ // Now redirect to the target.
+ return make_pair(true, st->sendRedirect(target));
+}
--- /dev/null
+/*
+ * Copyright 2001-2005 Internet2
+ *
+ * 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.
+ */
+
+/* internal.h
+
+ Scott Cantor
+ 2/14/04
+
+ $History:$
+*/
+
+#ifndef __internal_h__
+#define __internal_h__
+
+#include <saml/saml.h>
+#include <shib/shib.h>
+#include <shib-target/shib-target.h>
+#include <shib-target/hresult.h>
+
+#include <log4cpp/Category.hh>
+
+#define ADFS_LOGCAT "shibtarget"
+#define SHIBTRAN_LOGCAT "Shibboleth-TRANSACTION"
+#define ADFS_L(s) adfs::XML::Literals::s
+
+namespace adfs {
+
+ class XML
+ {
+ public:
+ // URI constants
+ static const XMLCh WSFED_NS[]; // http://schemas.xmlsoap.org/ws/2003/07/secext
+ static const XMLCh WSTRUST_NS[]; // http://schemas.xmlsoap.org/ws/2005/02/trust
+ static const XMLCh WSTRUST_SCHEMA_ID[];
+
+ struct Literals
+ {
+ static const XMLCh RequestedSecurityToken[];
+ static const XMLCh RequestSecurityTokenResponse[];
+ };
+ };
+
+ // TODO: Publish these classes for reuse by extensions.
+ class CgiParse
+ {
+ public:
+ CgiParse(const char* data, unsigned int len);
+ ~CgiParse();
+ const char* get_value(const char* name) const;
+
+ static char x2c(char *what);
+ static void url_decode(char *url);
+ static std::string url_encode(const char* s);
+ private:
+ char * fmakeword(char stop, unsigned int *cl, const char** ppch);
+ char * makeword(char *line, char stop);
+ void plustospace(char *str);
+
+ std::map<std::string,char*> kvp_map;
+ };
+
+ // Helper class for SAML 2.0 Common Domain Cookie operations
+ class CommonDomainCookie
+ {
+ public:
+ CommonDomainCookie(const char* cookie);
+ ~CommonDomainCookie() {}
+ saml::Iterator<std::string> get() {return m_list;}
+ const char* set(const char* providerId);
+ static const char CDCName[];
+ private:
+ std::string m_encoded;
+ std::vector<std::string> m_list;
+ };
+
+ saml::SAMLAuthenticationStatement* checkAssertionProfile(const saml::SAMLAssertion* a);
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright 2001-2005 Internet2
+ *
+ * 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.
+ */
+
+/*
+ * listener.cpp -- implementation of IListener functional methods that includes ADFS support
+ *
+ * Scott Cantor
+ * 10/10/05
+ *
+ */
+
+#include "internal.h"
+
+#include <xercesc/framework/MemBufInputSource.hpp>
+
+using namespace std;
+using namespace log4cpp;
+using namespace saml;
+using namespace shibboleth;
+using namespace shibtarget;
+using namespace adfs;
+
+namespace {
+ class ADFSListener : public virtual IListener
+ {
+ public:
+ ADFSListener(const DOMElement* e) : log(&Category::getInstance(ADFS_LOGCAT".Listener")) {}
+ ~ADFSListener() {}
+
+ bool create(ShibSocket& s) const {return true;}
+ bool bind(ShibSocket& s, bool force=false) const {return true;}
+ bool connect(ShibSocket& s) const {return true;}
+ bool close(ShibSocket& s) const {return true;}
+ bool accept(ShibSocket& listener, ShibSocket& s) const {return true;}
+
+ void sessionNew(
+ const IApplication* application,
+ int supported_profiles,
+ const char* recipient,
+ const char* packet,
+ const char* ip,
+ std::string& target,
+ std::string& cookie,
+ std::string& provider_id
+ ) const;
+
+ void sessionGet(
+ const IApplication* application,
+ const char* cookie,
+ const char* ip,
+ ISessionCacheEntry** pentry
+ ) const;
+
+ void sessionEnd(
+ const IApplication* application,
+ const char* cookie
+ ) const;
+
+ void ping(int& i) const;
+
+ private:
+ Category* log;
+ };
+}
+
+IPlugIn* ADFSListenerFactory(const DOMElement* e)
+{
+ return new ADFSListener(e);
+}
+
+void ADFSListener::sessionNew(
+ const IApplication* app,
+ int supported_profiles,
+ const char* recipient,
+ const char* packet,
+ const char* ip,
+ string& target,
+ string& cookie,
+ string& provider_id
+ ) const
+{
+#ifdef _DEBUG
+ saml::NDC ndc("sessionNew");
+#endif
+
+ log->debug("creating session for %s", ip);
+ log->debug("recipient: %s", recipient);
+ log->debug("application: %s", app->getId());
+
+ auto_ptr_XMLCh wrecipient(recipient);
+
+ // Access the application config. It's already locked behind us.
+ ShibTargetConfig& stc=ShibTargetConfig::getConfig();
+ IConfig* conf=stc.getINI();
+
+ bool checkIPAddress=true;
+ const IPropertySet* props=app->getPropertySet("Sessions");
+ if (props) {
+ pair<bool,bool> pcheck=props->getBool("checkAddress");
+ if (pcheck.first)
+ checkIPAddress = pcheck.second;
+ }
+
+ pair<bool,bool> checkReplay=pair<bool,bool>(false,false);
+ props=app->getPropertySet("Sessions");
+ if (props)
+ checkReplay=props->getBool("checkReplay");
+
+ const IRoleDescriptor* role=NULL;
+ Metadata m(app->getMetadataProviders());
+
+ bool bADFS = false;
+ SAMLBrowserProfile::BrowserProfileResponse bpr;
+
+ // For now, just branch off to handle ADFS inline, I'll wrap all this up later.
+ if (supported_profiles & ADFS_SSO) {
+ log->debug("executing ADFS profile...");
+ CgiParse parser(packet,strlen(packet));
+ const char* param=parser.get_value("wa");
+ if (param && !strcmp(param,"wsignin1.0")) {
+ bADFS=true;
+ param=parser.get_value("wresult");
+ if (!param)
+ throw FatalProfileException("ADFS profile required wresult parameter not found");
+
+ log->debug("decoded ADFS Token response:\n%s",param);
+ // wresult should carry an wst:RequestSecurityTokenResponse message so we parse it manually
+ DOMDocument* rdoc=NULL;
+ try {
+ saml::XML::Parser p;
+ static const XMLCh systemId[]={chLatin_W, chLatin_S, chDash, chLatin_T, chLatin_r, chLatin_u, chLatin_s, chLatin_t, chNull};
+ MemBufInputSource membufsrc(reinterpret_cast<const XMLByte*>(param),strlen(param),systemId,false);
+ Wrapper4InputSource dsrc(&membufsrc,false);
+ rdoc=p.parse(dsrc);
+
+ // Process the wrapper and extract the assertion.
+ if (saml::XML::isElementNamed(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestSecurityTokenResponse))) {
+ DOMElement* e=
+ saml::XML::getFirstChildElement(rdoc->getDocumentElement(),adfs::XML::WSTRUST_NS,ADFS_L(RequestedSecurityToken));
+ if (e) {
+ e=saml::XML::getFirstChildElement(e,saml::XML::SAML_NS,L(Assertion));
+ if (e) {
+ auto_ptr<SAMLAssertion> assertion(new SAMLAssertion(e));
+
+ // Try and map to metadata.
+ const IEntityDescriptor* provider=m.lookup(assertion->getIssuer());
+ if (provider)
+ role=provider->getIDPSSODescriptor(adfs::XML::WSFED_NS);
+ if (!role) {
+ MetadataException ex("unable to locate role-specific metadata for identity provider.");
+ annotateException(&ex,provider); // throws it
+ }
+
+ try {
+ // Check over the assertion.
+ SAMLAuthenticationStatement* authnStatement=checkAssertionProfile(assertion.get());
+
+ // Check signature.
+ log->debug("passing signed ADFS assertion to trust layer");
+ Trust t(app->getTrustProviders());
+ if (!t.validate(*(assertion.get()),role)) {
+ log->error("unable to verify signed authentication assertion");
+ throw TrustException("unable to verify signed authentication assertion");
+ }
+
+ // Wrap the assertion in a dummy samlp:Response for subsequent processing.
+ // Generate the Response DOM using the assertion's document and then
+ // transfer ownership of the tree to the Response.
+ auto_ptr<SAMLResponse> response(new SAMLResponse());
+ response->addAssertion(assertion.release());
+ response->toDOM(rdoc);
+ response->setDocument(rdoc);
+ rdoc=NULL;
+
+ // Now dummy up the SAML profile response wrapper.
+ param=parser.get_value("wctx");
+ if (param)
+ bpr.TARGET=param;
+ bpr.profile=SAMLBrowserProfile::Post; // not really, but...
+ bpr.response=response.release();
+ bpr.assertion=response->getAssertions().next();
+ bpr.authnStatement=authnStatement;
+ }
+ catch (SAMLException& ex) {
+ annotateException(&ex,role); // throws it
+ }
+ }
+ }
+ }
+ if (rdoc) {
+ rdoc->release();
+ rdoc=NULL;
+ }
+ }
+ catch(...) {
+ if (rdoc) rdoc->release();
+ throw;
+ }
+ }
+ if (bADFS && !bpr.response)
+ throw FatalProfileException("ADFS profile was indicated, but processing was unsuccesful");
+ }
+
+ // If ADFS wasn't used, proceed to SAML processing up until we reach a common point.
+ int minorVersion = 1;
+ try {
+ if (!bADFS) {
+ int allowed = 0;
+ if (supported_profiles & SAML11_POST || supported_profiles & SAML10_POST)
+ allowed |= SAMLBrowserProfile::Post;
+ if (supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML10_ARTIFACT)
+ allowed |= SAMLBrowserProfile::Artifact;
+ minorVersion=(supported_profiles & SAML11_ARTIFACT || supported_profiles & SAML11_POST) ? 1 : 0;
+
+ auto_ptr<SAMLBrowserProfile::ArtifactMapper> artifactMapper(app->getArtifactMapper());
+
+ // Try and run the profile.
+ log->debug("executing browser profile...");
+ bpr=app->getBrowserProfile()->receive(
+ packet,
+ wrecipient.get(),
+ allowed,
+ (!checkReplay.first || checkReplay.second) ? conf->getReplayCache() : NULL,
+ artifactMapper.get(),
+ minorVersion
+ );
+
+ // Blow it away to clear any locks that might be held.
+ delete artifactMapper.release();
+
+ // Try and map to metadata (again).
+ // Once the metadata layer is in the SAML core, the repetition should be fixed.
+ const IEntityDescriptor* provider=m.lookup(bpr.assertion->getIssuer());
+ if (!provider && bpr.authnStatement->getSubject()->getNameIdentifier() &&
+ bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier())
+ provider=m.lookup(bpr.authnStatement->getSubject()->getNameIdentifier()->getNameQualifier());
+ if (provider) {
+ const IIDPSSODescriptor* IDP=provider->getIDPSSODescriptor(
+ minorVersion==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
+ );
+ role=IDP;
+ }
+
+ // This isn't likely, since the profile must have found a role.
+ if (!role) {
+ MetadataException ex("Unable to locate role-specific metadata for identity provider.");
+ annotateException(&ex,provider); // throws it
+ }
+ }
+
+ // At this point, we link back up and do the same work for ADFS and SAML.
+
+ // Maybe verify the origin address....
+ if (checkIPAddress) {
+ log->debug("verifying client address");
+ // Verify the client address exists
+ const XMLCh* wip = bpr.authnStatement->getSubjectIP();
+ if (wip && *wip) {
+ // Verify the client address matches authentication
+ auto_ptr_char this_ip(ip);
+ if (strcmp(ip, this_ip.get())) {
+ FatalProfileException ex(
+ SESSION_E_ADDRESSMISMATCH,
+ "Your client's current address ($1) differs from the one used when you authenticated "
+ "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
+ "Please contact your local support staff or help desk for assistance.",
+ params(1,ip)
+ );
+ annotateException(&ex,role); // throws it
+ }
+ }
+ }
+
+ // Verify condition(s) on authentication assertion.
+ // Attribute assertions get filtered later by the AAP.
+ Iterator<SAMLCondition*> conditions=bpr.assertion->getConditions();
+ while (conditions.hasNext()) {
+ SAMLCondition* cond=conditions.next();
+ const SAMLAudienceRestrictionCondition* ac=dynamic_cast<const SAMLAudienceRestrictionCondition*>(cond);
+ if (!ac) {
+ ostringstream os;
+ os << *cond;
+ log->error("Unrecognized Condition in authentication assertion (%s), tossing it.",os.str().c_str());
+ FatalProfileException ex("unable to create session due to unrecognized condition in authentication assertion.");
+ annotateException(&ex,role); // throws it
+ }
+ else if (!ac->eval(app->getAudiences())) {
+ ostringstream os;
+ os << *ac;
+ log->error("Unacceptable AudienceRestrictionCondition in authentication assertion (%s), tossing it.",os.str().c_str());
+ FatalProfileException ex("unable to create session due to unacceptable AudienceRestrictionCondition in authentication assertion.");
+ annotateException(&ex,role); // throws it
+ }
+ }
+ }
+ catch (SAMLException&) {
+ bpr.clear();
+ throw;
+ }
+ catch (...) {
+ log->error("caught unknown exception");
+ bpr.clear();
+#ifdef _DEBUG
+ throw;
+#else
+ SAMLException e("An unexpected error occurred while creating your session.");
+ annotateException(&e,role);
+#endif
+ }
+
+ // It passes all our tests -- create a new session.
+ log->info("creating new session");
+
+ // Are attributes present?
+ bool attributesPushed=false;
+ Iterator<SAMLAssertion*> assertions=bpr.response->getAssertions();
+ while (!attributesPushed && assertions.hasNext()) {
+ Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
+ while (!attributesPushed && statements.hasNext()) {
+ if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
+ attributesPushed=true;
+ }
+ }
+
+ auto_ptr_char oname(role->getEntityDescriptor()->getId());
+ auto_ptr_char hname(bpr.authnStatement->getSubject()->getNameIdentifier()->getName());
+
+ try {
+ // Create a new session key.
+ cookie = conf->getSessionCache()->generateKey();
+
+ // Insert into cache.
+ auto_ptr<SAMLAuthenticationStatement> as(static_cast<SAMLAuthenticationStatement*>(bpr.authnStatement->clone()));
+ conf->getSessionCache()->insert(
+ cookie.c_str(),
+ app,
+ ip,
+ (bADFS ? ADFS_SSO :
+ ((bpr.profile==SAMLBrowserProfile::Post) ?
+ (minorVersion==1 ? SAML11_POST : SAML10_POST) : (minorVersion==1 ? SAML11_ARTIFACT : SAML10_ARTIFACT))),
+ oname.get(),
+ as.get(),
+ (attributesPushed ? bpr.response : NULL),
+ role
+ );
+ as.release(); // owned by cache now
+ }
+ catch (SAMLException&) {
+ bpr.clear();
+ throw;
+ }
+ catch (...) {
+ log->error("caught unknown exception");
+ bpr.clear();
+#ifdef _DEBUG
+ throw;
+#else
+ SAMLException e("An unexpected error occurred while creating your session.");
+ annotateException(&e,role);
+#endif
+ }
+
+ target = bpr.TARGET;
+ provider_id = oname.get();
+
+ // Maybe delete the response...
+ if (!attributesPushed)
+ bpr.clear();
+
+ log->debug("new session id: %s", cookie.c_str());
+
+ // Transaction Logging
+ Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
+ "New session (ID: " <<
+ cookie <<
+ ") with (applicationId: " <<
+ app->getId() <<
+ ") for principal from (IdP: " <<
+ provider_id <<
+ ") at (ClientAddress: " <<
+ ip <<
+ ") with (NameIdentifier: " <<
+ hname.get() <<
+ ")";
+ //stc.releaseTransactionLog();
+}
+
+void ADFSListener::sessionGet(
+ const IApplication* app,
+ const char* cookie,
+ const char* ip,
+ ISessionCacheEntry** pentry
+ ) const
+{
+#ifdef _DEBUG
+ saml::NDC ndc("sessionGet");
+#endif
+
+ *pentry=NULL;
+ log->debug("checking for session: %s@%s", cookie, ip);
+
+ // See if the session exists...
+
+ ShibTargetConfig& stc=ShibTargetConfig::getConfig();
+ IConfig* conf=stc.getINI();
+ log->debug("application: %s", app->getId());
+
+ bool checkIPAddress=true;
+ int lifetime=0,timeout=0;
+ const IPropertySet* props=app->getPropertySet("Sessions");
+ if (props) {
+ pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
+ if (p.first)
+ lifetime = p.second;
+ p=props->getUnsignedInt("timeout");
+ if (p.first)
+ timeout = p.second;
+ pair<bool,bool> pcheck=props->getBool("checkAddress");
+ if (pcheck.first)
+ checkIPAddress = pcheck.second;
+ }
+
+ *pentry = conf->getSessionCache()->find(cookie,app);
+
+ // If not, leave now..
+ if (!*pentry) {
+ log->debug("session not found");
+ throw InvalidSessionException("No session exists for key value ($session_id)",namedparams(1,"session_id",cookie));
+ }
+
+ // TEST the session...
+ try {
+ // Verify the address is the same
+ if (checkIPAddress) {
+ log->debug("Checking address against %s", (*pentry)->getClientAddress());
+ if (strcmp(ip, (*pentry)->getClientAddress())) {
+ log->debug("client address mismatch");
+ InvalidSessionException ex(
+ SESSION_E_ADDRESSMISMATCH,
+ "Your IP address (%1) does not match the address recorded at the time the session was established.",
+ params(1,ip)
+ );
+ Metadata m(app->getMetadataProviders());
+ annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
+ }
+ }
+
+ // and that the session is still valid...
+ if (!(*pentry)->isValid(lifetime,timeout)) {
+ log->debug("session expired");
+ InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
+ Metadata m(app->getMetadataProviders());
+ annotateException(&ex,m.lookup((*pentry)->getProviderId())); // throws it
+ }
+ }
+ catch (SAMLException&) {
+ (*pentry)->unlock();
+ *pentry=NULL;
+ conf->getSessionCache()->remove(cookie);
+
+ // Transaction Logging
+ Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
+ "Destroyed invalid session (ID: " <<
+ cookie <<
+ ") with (applicationId: " <<
+ app->getId() <<
+ "), request was from (ClientAddress: " <<
+ ip <<
+ ")";
+ //stc.releaseTransactionLog();
+ throw;
+ }
+ catch (...) {
+ log->error("caught unknown exception");
+#ifndef _DEBUG
+ InvalidSessionException ex("An unexpected error occurred while validating your session, and you must re-authenticate.");
+ Metadata m(app->getMetadataProviders());
+ annotateException(&ex,m.lookup((*pentry)->getProviderId()),false);
+#endif
+ (*pentry)->unlock();
+ *pentry=NULL;
+ conf->getSessionCache()->remove(cookie);
+
+ // Transaction Logging
+ Category::getInstance(SHIBTRAN_LOGCAT).infoStream() <<
+ "Destroyed invalid session (ID: " <<
+ cookie <<
+ ") with (applicationId: " <<
+ app->getId() <<
+ "), request was from (ClientAddress: " <<
+ ip <<
+ ")";
+ //stc.releaseTransactionLog();
+#ifdef _DEBUG
+ throw;
+#else
+ ex.raise();
+#endif
+ }
+
+ log->debug("session ok");
+}
+
+void ADFSListener::sessionEnd(
+ const IApplication* application,
+ const char* cookie
+ ) const
+{
+#ifdef _DEBUG
+ saml::NDC ndc("sessionEnd");
+#endif
+
+ log->debug("removing session: %s", cookie);
+
+ ShibTargetConfig& stc=ShibTargetConfig::getConfig();
+ stc.getINI()->getSessionCache()->remove(cookie);
+
+ // Transaction Logging
+ Category::getInstance(SHIBTRAN_LOGCAT).infoStream() << "Destroyed session (ID: " << cookie << ")";
+ //stc.releaseTransactionLog();
+}
+
+void ADFSListener::ping(int& i) const
+{
+ i++;
+}
--- /dev/null
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by adfs.rc
+//
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif