/*++ Copyright (c) 1999 Microsoft Corporation Module Name : passportprovider.cxx Abstract: Core passport authentication support Author: Bilal Alam (balam) 16-Mar-2001 Environment: Win32 - User Mode Project: ULW3.DLL --*/ #include "precomp.hxx" #include "passportprovider.hxx" #define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} #define MAGIC_TWEENER_STRING L"msppchlg=1&mspplogin=" #define MAGIC_TWEENER_STRING_LEN ( sizeof( MAGIC_TWEENER_STRING ) / sizeof( WCHAR ) - 1 ) MIDL_DEFINE_GUID(IID,IID_IPassportManager3,0x1451151f,0x90a0,0x491b,0xb8,0xe1,0x81,0xa1,0x37,0x67,0xed,0x98); MIDL_DEFINE_GUID(IID,IID_IPassportFactory,0x5602E147,0x27F6,0x11d3,0x94,0xDD,0x00,0xC0,0x4F,0x72,0xDC,0x08); MIDL_DEFINE_GUID(CLSID,CLSID_PassportFactory,0x74EB2514,0xE239,0x11D2,0x95,0xE9,0x00,0xC0,0x4F,0x8E,0x7A,0x70); IPassportFactory * PASSPORT_CONTEXT::sm_pPassportManagerFactory; BSTR PASSPORT_CONTEXT::sm_bstrMemberIdHigh; BSTR PASSPORT_CONTEXT::sm_bstrMemberIdLow; BSTR PASSPORT_CONTEXT::sm_bstrReturnUrl; BSTR PASSPORT_CONTEXT::sm_bstrTimeWindow; BSTR PASSPORT_CONTEXT::sm_bstrForceSignIn; BSTR PASSPORT_CONTEXT::sm_bstrCoBrandTemplate; BSTR PASSPORT_CONTEXT::sm_bstrLanguageId; BSTR PASSPORT_CONTEXT::sm_bstrSecureLevel; //static HRESULT PASSPORT_CONTEXT::Initialize( VOID ) /*++ Routine Description: Do global initialization for passport goo Arguments: None Return Value: HRESULT --*/ { HRESULT hr = NO_ERROR; // // Pre-allocate some BSTRs // sm_bstrMemberIdHigh = SysAllocString( L"MemberIdHigh" ); if ( sm_bstrMemberIdHigh == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } sm_bstrMemberIdLow = SysAllocString( L"MemberIdLow" ); if ( sm_bstrMemberIdLow == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } sm_bstrReturnUrl = SysAllocString( L"ReturnURL" ); if ( sm_bstrReturnUrl == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } sm_bstrTimeWindow = SysAllocString( L"TimeWindow" ); if ( sm_bstrTimeWindow == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } sm_bstrForceSignIn = SysAllocString( L"ForceSignIn" ); if ( sm_bstrForceSignIn == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } sm_bstrCoBrandTemplate = SysAllocString( L"CoBrandTemplate" ); if ( sm_bstrCoBrandTemplate == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } sm_bstrLanguageId = SysAllocString( L"LanguageId" ); if ( sm_bstrLanguageId == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } sm_bstrSecureLevel = SysAllocString( L"SecureLevel" ); if ( sm_bstrSecureLevel == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } // // Try to initialize the passport manager factory. If we cannot, then // we're done // hr = CoInitializeEx( NULL, COINIT_MULTITHREADED ); if( FAILED( hr ) ) { goto Failure; } hr = CoCreateInstance( CLSID_PassportFactory, NULL, CLSCTX_INPROC_SERVER, IID_IPassportFactory, (void**)&sm_pPassportManagerFactory ); CoUninitialize(); if ( FAILED( hr ) ) { goto Failure; } DBG_ASSERT( sm_pPassportManagerFactory != NULL ); return NO_ERROR; Failure: if ( FAILED( hr ) ) { if ( sm_pPassportManagerFactory != NULL ) { sm_pPassportManagerFactory->Release(); sm_pPassportManagerFactory = NULL; } if ( sm_bstrMemberIdLow != NULL ) { SysFreeString( sm_bstrMemberIdLow ); sm_bstrMemberIdLow = NULL; } if ( sm_bstrMemberIdHigh != NULL ) { SysFreeString( sm_bstrMemberIdHigh ); sm_bstrMemberIdHigh = NULL; } if ( sm_bstrReturnUrl != NULL ) { SysFreeString( sm_bstrReturnUrl ); sm_bstrReturnUrl = NULL; } if ( sm_bstrTimeWindow == NULL ) { SysFreeString( sm_bstrTimeWindow ); sm_bstrTimeWindow = NULL; } if ( sm_bstrForceSignIn == NULL ) { SysFreeString( sm_bstrForceSignIn ); sm_bstrForceSignIn = NULL; } if ( sm_bstrCoBrandTemplate == NULL ) { SysFreeString( sm_bstrCoBrandTemplate ); sm_bstrCoBrandTemplate = NULL; } if ( sm_bstrLanguageId == NULL ) { SysFreeString( sm_bstrLanguageId ); sm_bstrLanguageId = NULL; } if ( sm_bstrSecureLevel == NULL ) { SysFreeString( sm_bstrSecureLevel ); sm_bstrSecureLevel = NULL; } } return hr; } //static VOID PASSPORT_CONTEXT::Terminate( VOID ) /*++ Routine Description: Cleanup global passport goo Arguments: None Return Value: None --*/ { if ( sm_pPassportManagerFactory != NULL ) { sm_pPassportManagerFactory->Release(); sm_pPassportManagerFactory = NULL; } if ( sm_bstrMemberIdLow != NULL ) { SysFreeString( sm_bstrMemberIdLow ); sm_bstrMemberIdLow = NULL; } if ( sm_bstrMemberIdHigh != NULL ) { SysFreeString( sm_bstrMemberIdHigh ); sm_bstrMemberIdHigh = NULL; } if ( sm_bstrReturnUrl != NULL ) { SysFreeString( sm_bstrReturnUrl ); sm_bstrReturnUrl = NULL; } if ( sm_bstrTimeWindow == NULL ) { SysFreeString( sm_bstrTimeWindow ); sm_bstrTimeWindow = NULL; } if ( sm_bstrForceSignIn == NULL ) { SysFreeString( sm_bstrForceSignIn ); sm_bstrForceSignIn = NULL; } if ( sm_bstrCoBrandTemplate == NULL ) { SysFreeString( sm_bstrCoBrandTemplate ); sm_bstrCoBrandTemplate = NULL; } if ( sm_bstrLanguageId == NULL ) { SysFreeString( sm_bstrLanguageId ); sm_bstrLanguageId = NULL; } if ( sm_bstrSecureLevel == NULL ) { SysFreeString( sm_bstrSecureLevel ); sm_bstrSecureLevel = NULL; } } BOOL PASSPORT_CONTEXT::QueryUserError( VOID ) /*++ Routine Description: Returns whether the user cancelled. That we have to do this work really sucks Arguments: None Return Value: BOOL --*/ { LONG lError; HRESULT hr; if ( _pPassportManager == NULL ) { return FALSE; } hr = _pPassportManager->get_Error( &lError ); if ( FAILED( hr ) ) { return FALSE; } return lError != 0; } HRESULT PASSPORT_CONTEXT::SetupDefaultRedirect( W3_MAIN_CONTEXT * pMainContext, BOOL * pfFoundRedirect ) /*++ Routine Description: Setup default redirect in case of client cancelling Arguments: pMainContext - main context pfFoundRedirect - Set to TRUE if redirect URL found Return Value: HRESULT --*/ { HRESULT hr; VARIANT vReturnUrl; STACK_STRU( strRedirect, 512 ); VariantInit( &vReturnUrl ); if ( pMainContext == NULL || pfFoundRedirect == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *pfFoundRedirect = FALSE; DBG_ASSERT( _pPassportManager != NULL ); // // First get the default URL if any // hr = _pPassportManager->GetCurrentConfig( sm_bstrReturnUrl, &vReturnUrl ); if ( FAILED( hr ) ) { return NO_ERROR; } if ( vReturnUrl.vt != VT_BSTR || vReturnUrl.bstrVal[ 0 ] == L'\0' ) { return NO_ERROR; } // // Do the redirect // hr = strRedirect.Copy( vReturnUrl.bstrVal ); if ( FAILED( hr ) ) { return hr; } hr = pMainContext->SetupHttpRedirect( strRedirect, TRUE, HttpStatusRedirect ); if ( FAILED( hr ) ) { return hr; } *pfFoundRedirect = TRUE; return NO_ERROR; } HRESULT PASSPORT_CONTEXT::Create( W3_FILTER_CONTEXT * pFilterContext ) /*++ Routine Description: Initialize a passport filter context -the big thing being getting a passport manager for this request Arguments: pFilterContext - Filter context Return Value: HRESULT --*/ { IDispatch * pDispatch = NULL; HRESULT hr; DWORD cbBufferLength; if ( sm_pPassportManagerFactory == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ); } // // Do some COM/OLEAUT crap to get a passport manager // hr = sm_pPassportManagerFactory->CreatePassportManager( &pDispatch ); if ( FAILED( hr ) ) { return hr; } DBG_ASSERT( pDispatch != NULL ); hr = pDispatch->QueryInterface( IID_IPassportManager3, (VOID**) &_pPassportManager ); pDispatch->Release(); if ( FAILED( hr ) ) { return hr; } DBG_ASSERT( _pPassportManager != NULL ); // // Try a cookie of size 4096 since the samples all seem to use that size // if ( !_buffCookie.Resize( 4096 ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } cbBufferLength = _buffCookie.QuerySize(); // // Pass filter context to passport manager so that it can inspect the // request // DBG_ASSERT( _pPassportManager != NULL ); hr = _pPassportManager->OnStartPageFilter( (PBYTE) pFilterContext->QueryFC(), &cbBufferLength, (LPSTR) _buffCookie.QueryPtr() ); if ( FAILED( hr ) ) { return hr; } return NO_ERROR; } HRESULT PASSPORT_CONTEXT::DoesApply( HTTP_FILTER_CONTEXT * pfc, BOOL * pfApplies, STRA * pstrReturnCookie ) /*++ Routine Description: Check whether the given request has Passport stuff in it Arguments: pfc - Filter context pfApplies - Set to TRUE if passport applies pstrReturnCookie - Cookie to return in response Return Value: HRESULT --*/ { HRESULT hr; VARIANT vTimeWindow; VARIANT vForceLogin; VARIANT vSecureLevel; VARIANT_BOOL vb; BUFFER bufReturnCookie; if ( pfc == NULL || pfApplies == NULL || pstrReturnCookie == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *pfApplies = FALSE; // // Read parameters for IsAuthenticated(). If we can't find // them then choose arbitrary (tm) defaults // VariantInit( &vTimeWindow ); hr = _pPassportManager->GetCurrentConfig( sm_bstrTimeWindow, &vTimeWindow ); if ( FAILED( hr ) ) { vTimeWindow.vt = VT_I4; vTimeWindow.lVal = 10000; } VariantInit( &vForceLogin ); hr = _pPassportManager->GetCurrentConfig( sm_bstrForceSignIn, &vForceLogin ); if ( FAILED( hr ) ) { vForceLogin.vt = VT_BOOL; vForceLogin.boolVal = VARIANT_FALSE; } VariantInit( &vSecureLevel ); hr = _pPassportManager->GetCurrentConfig( sm_bstrSecureLevel, &vSecureLevel ); if ( FAILED( hr ) ) { vSecureLevel.vt = VT_I4; vSecureLevel.lVal = 10; } // // Are we authenticated? // hr = _pPassportManager->IsAuthenticated( vTimeWindow, vForceLogin, vSecureLevel, &vb ); if ( FAILED( hr ) ) { return hr; } if ( vb == VARIANT_TRUE ) { _fAuthenticated = TRUE; } *pfApplies = _fAuthenticated; return pstrReturnCookie->Copy( (CHAR*) _buffCookie.QueryPtr() ); } HRESULT PASSPORT_CONTEXT::DoAuthenticate( W3_MAIN_CONTEXT * pMainContext, TOKEN_CACHE_ENTRY ** ppCachedToken, STRU * pstrAuthUser, STRU * pstrRemoteUser, STRU & strDomainName ) /*++ Routine Description: Logon the passport user (i.e. get a token) Arguments: pMetaData - Metadata for this request ppCachedToken - Filled with token cache entry represented mapped user pstrAuthUser - Filled with AUTH_USER pstrRemoteUser - Filled with PUID strDomainName - Domain name Return Value: HRESULT --*/ { VARIANT vMemberId; HRESULT hr; WCHAR achLarge[ 64 ]; DWORD dwLogonError; TOKEN_CACHE_ENTRY * pCachedToken = NULL; LONG lLowPuid; LONG lHighPuid; BOOL fRet; HANDLE hToken; STACK_BUFFER( bufName, 512 ); DWORD cchName; W3_METADATA * pMetaData; if ( pMainContext == NULL || ppCachedToken == NULL || pstrAuthUser == NULL || pstrRemoteUser == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } pMetaData = pMainContext->QueryUrlContext()->QueryMetaData(); DBG_ASSERT( pMetaData != NULL ); // // Get the PUID -> this is the remote user name. // Start with the high part // VariantInit( &vMemberId ); DBG_ASSERT( _pPassportManager != NULL ); hr = _pPassportManager->get_Profile( sm_bstrMemberIdHigh, &vMemberId ); if ( FAILED( hr ) ) { goto Failure; } lHighPuid = V_I4( &vMemberId ); // // Next the low part // hr = _pPassportManager->get_Profile( sm_bstrMemberIdLow, &vMemberId ); if ( FAILED( hr ) ) { goto Failure; } lLowPuid = V_I4( &vMemberId ); // // Now make a string out of the QuadPart // wsprintfW( achLarge, L"%08X%08X", lHighPuid, lLowPuid ); // // The REMOTE_USER server variable is always PUID@domain. // hr = pstrRemoteUser->Copy( achLarge ); if ( FAILED( hr ) ) { goto Failure; } hr = pstrRemoteUser->Append( L"@" ); if ( FAILED( hr ) ) { goto Failure; } hr = pstrRemoteUser->Append( strDomainName.QueryStr() ); if ( FAILED( hr ) ) { goto Failure; } // // Should we be doing mapping at all? // if ( pMetaData->QueryRequireMapping() == MD_PASSPORT_NO_MAPPING ) { // // No mapping. Just use anonymous. // hr = pMetaData->GetAndRefAnonymousToken( &pCachedToken ); if( FAILED( hr ) ) { return hr; } if ( pCachedToken == NULL ) { return HRESULT_FROM_WIN32( ERROR_LOGON_FAILURE ); } pstrAuthUser->Reset(); *ppCachedToken = pCachedToken; return NO_ERROR; } // // If we got here then we must be doing mapping (trying it anyways) // // // Get the cached token (in other words, call into LsaLogonUser() ) // DBG_ASSERT( g_pW3Server->QueryTokenCache() != NULL ); hr = g_pW3Server->QueryTokenCache()->GetCachedToken( pstrRemoteUser->QueryStr(), L"", L"", ( DWORD )IIS_LOGON_METHOD_PASSPORT, FALSE, FALSE, pMainContext->QueryRequest()-> QueryRemoteSockAddress(), &pCachedToken, &dwLogonError ); if ( FAILED( hr ) ) { goto Failure; } // // Now, was mapping required or not??? (extra ??? for special effect) // if ( pCachedToken == NULL ) { if ( pMetaData->QueryRequireMapping() == MD_PASSPORT_NEED_MAPPING ) { // // No mapping -> fail! // return HRESULT_FROM_WIN32( dwLogonError ); } else { // // We tried, we failed, we'll persevere! // hr = pMetaData->GetAndRefAnonymousToken( &pCachedToken ); if ( FAILED( hr ) ) { return hr; } if ( pCachedToken == NULL ) { return HRESULT_FROM_WIN32( ERROR_LOGON_FAILURE ); } pstrAuthUser->Reset(); *ppCachedToken = pCachedToken; return NO_ERROR; } } // // The AUTH_USER server variable is the account name if mapping worked, else empty string // // // Get the token information by impersonating // (we could just cache this info, but then again who is going // to use this mapping feature anyways???) // hToken = pCachedToken->QueryImpersonationToken(); if ( hToken == NULL ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } fRet = ImpersonateLoggedOnUser( hToken ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } cchName = bufName.QuerySize() / sizeof( WCHAR ); fRet = GetUserNameExW( NameSamCompatible, (WCHAR*) bufName.QueryPtr(), &cchName ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); if ( hr == HRESULT_FROM_WIN32( ERROR_MORE_DATA ) ) { DBG_ASSERT( cchName > bufName.QuerySize() / sizeof( WCHAR ) ); fRet = bufName.Resize( cchName * sizeof( WCHAR ) ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); } else { fRet = GetUserNameExW( NameSamCompatible, (WCHAR*) bufName.QueryPtr(), &cchName ); if ( !fRet ) { hr = HRESULT_FROM_WIN32( GetLastError() ); } else { hr = NO_ERROR; } } } } // // Always revert // if ( !RevertToSelf() ) { hr = HRESULT_FROM_WIN32( GetLastError() ); goto Failure; } // // If we failed earlier, bail. // if ( FAILED( hr ) ) { goto Failure; } hr = pstrAuthUser->Copy( (WCHAR*) bufName.QueryPtr() ); if ( FAILED( hr ) ) { goto Failure; } if ( pCachedToken != NULL ) { *ppCachedToken = pCachedToken; } return NO_ERROR; Failure: DBG_ASSERT( FAILED( hr ) ); if ( pCachedToken != NULL ) { pCachedToken->DereferenceCacheEntry(); pCachedToken = NULL; } return hr; } HRESULT PASSPORT_CONTEXT::OnChallenge( STRU & strOriginalUrl ) /*++ Routine Description: Do a passport challenge Arguments: strOriginalUrl - Original URL Return Value: HRESULT --*/ { BSTR bstrOriginalUrl; VARIANT vReturnURL; VARIANT vTimeWindow; VARIANT vForceLogin; VARIANT vNoParam; VARIANT vCoBrandTemplate; VARIANT vSecureLevel; HRESULT hr = NO_ERROR; VariantInit( &vTimeWindow ); hr = _pPassportManager->GetCurrentConfig( sm_bstrTimeWindow, &vTimeWindow ); if ( FAILED( hr ) ) { vTimeWindow.vt = VT_I4; vTimeWindow.lVal = 10000; } VariantInit( &vForceLogin ); hr = _pPassportManager->GetCurrentConfig( sm_bstrForceSignIn, &vForceLogin ); if ( FAILED( hr ) ) { vForceLogin.vt = VT_BOOL; vForceLogin.boolVal = VARIANT_FALSE; } VariantInit( &vCoBrandTemplate ); hr = _pPassportManager->GetCurrentConfig( sm_bstrCoBrandTemplate, &vCoBrandTemplate ); if ( FAILED( hr ) ) { vCoBrandTemplate.vt = VT_ERROR; vCoBrandTemplate.scode = DISP_E_PARAMNOTFOUND; } VariantInit( &vSecureLevel ); hr = _pPassportManager->GetCurrentConfig( sm_bstrSecureLevel, &vSecureLevel ); if ( FAILED( hr ) ) { vSecureLevel.vt = VT_I4; vSecureLevel.lVal = 10; } // // Make this a secure return URL if needed (lame) // if ( vSecureLevel.lVal > 0 ) { if ( _wcsnicmp( strOriginalUrl.QueryStr(), L"https://", 8 ) != 0 ) { STACK_STRU( strTemp, 256 ); // // Must be a nonsecure url // DBG_ASSERT( _wcsnicmp( strOriginalUrl.QueryStr(), L"http://", 7 ) == 0 ); hr = strTemp.Copy( L"https://" ); if ( FAILED( hr ) ) { return hr; } hr = strTemp.Append( strOriginalUrl.QueryStr() + 7 ); if ( FAILED( hr ) ) { return hr; } bstrOriginalUrl = SysAllocString( strTemp.QueryStr() ); } else { // // Just use the original URL // bstrOriginalUrl = SysAllocString( strOriginalUrl.QueryStr() ); } } else { // // Just use the original URL // bstrOriginalUrl = SysAllocString( strOriginalUrl.QueryStr() ); } if ( bstrOriginalUrl == NULL ) { return HRESULT_FROM_WIN32( GetLastError() ); } VariantInit( &vNoParam ); VariantInit( &vReturnURL ); vReturnURL.vt = VT_BSTR; vReturnURL.bstrVal = bstrOriginalUrl; vNoParam.vt = VT_ERROR; vNoParam.scode = DISP_E_PARAMNOTFOUND; hr = _pPassportManager->LoginUser( vReturnURL, vTimeWindow, vForceLogin, vCoBrandTemplate, vNoParam, vNoParam, vNoParam, vSecureLevel, vNoParam ); if ( FAILED( hr ) ) { goto Finished; } Finished: VariantClear( &vReturnURL ); return hr; } HRESULT PASSPORT_AUTH_PROVIDER::Initialize( DWORD dwInternalId ) /*++ Routine Description: Initialize passport authentication provider Arguments: None Return Value: HRESULT --*/ { SetInternalId( dwInternalId ); // // We defer initialization of passport manager crap until we really // need it. Why? Because loading their DLLs causes a process-wide // perf hit with string compares. This hit is killing ASP perf // if( !INITIALIZE_CRITICAL_SECTION( &_csInitLock ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } _fInitialized = FALSE; return NO_ERROR; } VOID PASSPORT_AUTH_PROVIDER::Terminate( VOID ) /*++ Routine Description: Terminate passport authentication provider Arguments: None Return Value: None --*/ { if ( _fInitialized ) { PASSPORT_CONTEXT::Terminate(); _fInitialized = FALSE; } DeleteCriticalSection( &_csInitLock ); } HRESULT PASSPORT_AUTH_PROVIDER::DoesApply( W3_MAIN_CONTEXT * pMainContext, BOOL * pfApplies ) /*++ Routine Description: Check whether Passport applies to this request Arguments: pMainContext - Main context pfApplies - Set to TRUE if one of the filters indicates the request applies Return Value: HRESULT --*/ { URL_CONTEXT * pUrlContext; W3_METADATA * pMetaData; PASSPORT_CONTEXT * pPassportContext; W3_FILTER_CONTEXT * pFilterContext; HRESULT hr = S_OK; STACK_STRA( strReturnCookie, 256 ); BOOL fTweenerHandled = FALSE; if ( pMainContext == NULL || pfApplies == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *pfApplies = FALSE; // // Before we call into filters, check whether custom auth is enabled. // This is a departure from the other protocols, but is the practical // thing to do, since we don't want to call into arbitrary code (or // Passport Manager) on every request. // pUrlContext = pMainContext->QueryUrlContext(); DBG_ASSERT( pUrlContext != NULL ); pMetaData = pUrlContext->QueryMetaData(); DBG_ASSERT( pMetaData != NULL ); if ( !pMetaData->QueryAuthTypeSupported( MD_AUTH_PASSPORT ) ) { return NO_ERROR; } // // Ok. We need to do passport stuff. Initialize the passport stuff // now if needed // if ( !_fInitialized ) { EnterCriticalSection( &_csInitLock ); if ( !_fInitialized ) { hr = PASSPORT_CONTEXT::Initialize(); if ( SUCCEEDED( hr ) ) { _fInitialized = TRUE; } } LeaveCriticalSection( &_csInitLock ); } if ( !_fInitialized ) { DBG_ASSERT( FAILED( hr ) ); return hr; } // // Get a filter context since we'll need it now to ask passport manager // whether the current request applies // pFilterContext = pMainContext->QueryFilterContext(); if ( pFilterContext == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } // // Create a passport manager // pPassportContext = new (pMainContext) PASSPORT_CONTEXT; if ( pPassportContext == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } hr = pPassportContext->Create( pFilterContext ); if ( FAILED( hr ) ) { delete pPassportContext; return hr; } pMainContext->SetContextState( pPassportContext ); // // OK. Do some weird Tweener crap. Check the request for magic and if // we see it, we avoid Passport Manager altogether // hr = DoTweenerSpecialCase( pMainContext, &fTweenerHandled ); if ( FAILED( hr ) ) { return hr; } if ( fTweenerHandled ) { // // We have done our thing. Just return success. We'll let the // DoAuthenticate send the response // *pfApplies = TRUE; pPassportContext->SetTweener( TRUE ); return NO_ERROR; } // // Does this request look destined for passport? // hr = pPassportContext->DoesApply( pFilterContext->QueryFC(), pfApplies, &strReturnCookie ); if ( FAILED( hr ) ) { return hr; } // // // If a cookie was set, add it to the response // if ( !strReturnCookie.IsEmpty() ) { hr = pFilterContext->AddResponseHeaders( strReturnCookie.QueryStr() ); if ( FAILED( hr ) ) { return hr; } } return NO_ERROR; } HRESULT PASSPORT_AUTH_PROVIDER::DoAuthenticate( W3_MAIN_CONTEXT * pMainContext, BOOL * pfFilterFinished ) /*++ Routine Description: Allows filter which applies to actually authenticate the request Arguments: pMainContext - Main context pfFilterFinished - Set to TRUE if filter wants out Return Value: HRESULT --*/ { W3_METADATA * pMetaData; URL_CONTEXT * pUrlContext; TOKEN_CACHE_ENTRY * pToken = NULL; PASSPORT_USER_CONTEXT * pUserContext; HRESULT hr; PASSPORT_CONTEXT * pPassportContext; W3_RESPONSE * pResponse; STACK_STRU( strAuthUser, 256 ); STACK_STRU( strRemoteUser, 256 ); STACK_STRU( strDomainName, 256 ); if ( pMainContext == NULL || pfFilterFinished == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *pfFilterFinished = FALSE; // // We must be initialized! // DBG_ASSERT( _fInitialized ); // // We must be supported by metadata // pUrlContext = pMainContext->QueryUrlContext(); DBG_ASSERT( pUrlContext != NULL ); pMetaData = pUrlContext->QueryMetaData(); DBG_ASSERT( pMetaData != NULL ); DBG_ASSERT( pMetaData->QueryAuthTypeSupported( MD_AUTH_PASSPORT ) ); // // Get the saved passport state, we better be able to find it! // pPassportContext = (PASSPORT_CONTEXT*) pMainContext->QueryContextState(); DBG_ASSERT( pPassportContext != NULL ); // // Before we go any further, check whether we've already Tweenerized this // request. If we have, the response is already setup. Just bail // if ( pPassportContext->QueryIsTweener() ) { return NO_ERROR; } // // Choose a domain for the logon. If the metabase domain is set, use it, // otherwise choose the default domain name // if ( pMetaData->QueryDomainName() == NULL || pMetaData->QueryDomainName()[ 0 ] == L'\0' ) { // // If we're a member of a domain, use that domain name // if ( W3_STATE_AUTHENTICATION::QueryIsDomainMember() ) { hr = strDomainName.Copy( W3_STATE_AUTHENTICATION::QueryMemberDomainName() ); } else { hr = strDomainName.Copy( W3_STATE_AUTHENTICATION::QueryDefaultDomainName() ); } if ( FAILED( hr ) ) { return hr; } } else { // // Use the metabase domain name // hr = strDomainName.Copy( pMetaData->QueryDomainName() ); if ( FAILED( hr ) ) { return hr; } } // // Lets authenticate // hr = pPassportContext->DoAuthenticate( pMainContext, &pToken, &strAuthUser, &strRemoteUser, strDomainName ); if ( FAILED( hr ) ) { // // Setup the 401 response // DBG_ASSERT( pToken == NULL ); pMainContext->QueryResponse()->SetStatus( HttpStatusUnauthorized, Http401BadLogon ); return NO_ERROR; } // // Create a user context // pUserContext = new PASSPORT_USER_CONTEXT( this ); if ( pUserContext == NULL ) { pToken->DereferenceCacheEntry(); pToken = NULL; return HRESULT_FROM_WIN32( GetLastError() ); } hr = pUserContext->Create( pToken, strAuthUser, strRemoteUser ); if ( FAILED( hr ) ) { pToken->DereferenceCacheEntry(); pToken = NULL; pUserContext->DereferenceUserContext(); pUserContext = NULL; } pMainContext->SetUserContext( pUserContext ); return NO_ERROR; } HRESULT PASSPORT_AUTH_PROVIDER::EscapeAmpersands( STRA & strUrl ) /*++ Routine Description: Sigh. A special function to escape ampersands so passport is happy Arguments: strUrl - String to escape Return Value: HRESULT --*/ { STACK_STRA( strTemp, 256 ); HRESULT hr; CHAR * pszCursor; CHAR * pszAmpersand; pszCursor = strUrl.QueryStr(); pszAmpersand = strchr( pszCursor, '&' ); while ( pszAmpersand != NULL ) { hr = strTemp.Append( pszCursor, DIFF( pszAmpersand - pszCursor ) ); if ( FAILED( hr ) ) { return hr; } hr = strTemp.Append( "%26" ); if ( FAILED( hr ) ) { return hr; } pszCursor = pszAmpersand + 1; pszAmpersand = strchr( pszCursor, '&' ); } hr = strTemp.Append( pszCursor ); if ( FAILED( hr ) ) { return hr; } return strUrl.Copy( strTemp ); } HRESULT PASSPORT_AUTH_PROVIDER::OnAccessDenied( W3_MAIN_CONTEXT * pMainContext ) /*++ Routine Description: If we are logged on, present an acecss denied page. Otherwise, present the redirect Arguments: pMainContext - Main context Return Value: HRESULT --*/ { W3_METADATA * pMetaData; URL_CONTEXT * pUrlContext; HRESULT hr = S_OK; PASSPORT_CONTEXT * pPassportContext; STACK_STRU( strRedirect, 256 ); STACK_STRA( strReturnUrl, 256 ); STACK_STRU( strUnicodeReturnUrl, 256 ); W3_FILTER_CONTEXT * pFilterContext = NULL; STACK_STRA( strRawUrl, 256 ); W3_RESPONSE * pResponse; STACK_STRA( strAuthenticateHeader, 256 ); if ( pMainContext == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } // // We must be supported by metadata // pUrlContext = pMainContext->QueryUrlContext(); DBG_ASSERT( pUrlContext != NULL ); pMetaData = pUrlContext->QueryMetaData(); DBG_ASSERT( pMetaData != NULL ); DBG_ASSERT( pMetaData->QueryAuthTypeSupported( MD_AUTH_PASSPORT ) ); // // Sigh. Have to check whether a passport challenge is already // setup. If so, just NOP // pResponse = pMainContext->QueryResponse(); DBG_ASSERT( pResponse != NULL ); hr = pResponse->GetHeader( "WWW-Authenticate", &strAuthenticateHeader ); if ( SUCCEEDED( hr ) ) { if ( _strnicmp( strAuthenticateHeader.QueryStr(), "Passport1.4 ", 12 ) == 0 ) { // // Challenge already there. Just NOP // return NO_ERROR; } } // // In some cases (too complicated to explain), we might actually get // called on AccessDenied() before anything else. Therefore we do the // same deferred init here // if ( !_fInitialized ) { EnterCriticalSection( &_csInitLock ); if ( !_fInitialized ) { hr = PASSPORT_CONTEXT::Initialize(); if ( SUCCEEDED( hr ) ) { _fInitialized = TRUE; } } LeaveCriticalSection( &_csInitLock ); } if ( !_fInitialized ) { DBG_ASSERT( FAILED( hr ) ); return hr; } // // Get the saved passport state, we better be able to find it! // pPassportContext = (PASSPORT_CONTEXT*) pMainContext->QueryContextState( CONTEXT_STATE_AUTHENTICATION ); if ( pPassportContext == NULL ) { pFilterContext = pMainContext->QueryFilterContext(); if ( pFilterContext == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } // // Build a context now // pPassportContext = new (pMainContext) PASSPORT_CONTEXT; if ( pPassportContext == NULL ) { return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); } hr = pPassportContext->Create( pFilterContext ); if ( FAILED( hr ) ) { delete pPassportContext; return hr; } pMainContext->SetContextState( pPassportContext ); } // // If we have authenticated, then we'll want to send a local redirect // to an access denied page. Otherwise, we'll send a 302 redirect // if ( !pPassportContext->QueryIsAuthenticated() ) { // // Ok. Before we ask PassportManager for the authentication URL // to redirect to, first check whether the client just cancelled. If // they did we will redirect to the default page. // if ( pPassportContext->QueryUserError() ) { BOOL fDidRedirect = FALSE; hr = pPassportContext->SetupDefaultRedirect( pMainContext, &fDidRedirect ); if ( FAILED( hr ) || fDidRedirect ) { return hr; } // // If we're here, then there was no DefaultRedirect configured in passport // // Just set the passport forbidden error // pMainContext->QueryResponse()->SetStatus( HttpStatusForbidden, Http403PassportLoginFailure ); return NO_ERROR; } // // We want a URL without the extra :80 // hr = pMainContext->QueryRequest()->GetRawUrl( &strRawUrl ); if ( FAILED( hr ) ) { return hr; } hr = pMainContext->QueryRequest()->BuildFullUrl( strRawUrl, &strReturnUrl, FALSE ); if ( FAILED( hr ) ) { return hr; } hr = strReturnUrl.Escape(); if ( FAILED( hr ) ) { return hr; } hr = EscapeAmpersands( strReturnUrl ); if ( FAILED( hr ) ) { return hr; } hr = strUnicodeReturnUrl.CopyA( strReturnUrl.QueryStr() ); if ( FAILED( hr ) ) { return hr; } hr = pPassportContext->OnChallenge( strUnicodeReturnUrl ); if ( FAILED( hr ) ) { return hr; } } return NO_ERROR; } HRESULT PASSPORT_AUTH_PROVIDER::DoTweenerSpecialCase( W3_MAIN_CONTEXT * pMainContext, BOOL * pfTweenerHandled ) /*++ Routine Description: Handle Tweener crap Arguments: pMainContext - Main context pfTweenerHandled - Set to TRUE if tweener crap was done Return Value: HRESULT --*/ { STACK_STRU( strQueryString, 512 ); STACK_STRA( strTweenerUrl, 512 ); HRESULT hr; W3_RESPONSE * pResponse; W3_REQUEST * pRequest; STACK_STRA( strAuthenticateHeader, 512 ); CHAR * pszAuthenticateHeader = NULL; CHAR * pszStupid; CHAR * pszBizarre; CHAR * pszAcceptAuth; WCHAR * pszTweenerMagic; STACK_STRA( strAcceptAuth, 32 ); BOOL fSendRedirect = TRUE; if ( pMainContext == NULL || pfTweenerHandled == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } *pfTweenerHandled = FALSE; pResponse = pMainContext->QueryResponse(); DBG_ASSERT( pResponse != NULL ); pRequest = pMainContext->QueryRequest(); DBG_ASSERT( pRequest != NULL ); // // First check for a query string, if there isn't one, we're done // hr = pMainContext->QueryRequest()->GetQueryString( &strQueryString ); if ( FAILED( hr ) ) { return hr; } if ( strQueryString.QueryStr()[ 0 ] == L'\0' ) { // // No query string. We're done. // return NO_ERROR; } // // Check the query string for the magic "msppchlg=1&mspplogin=" string // pszTweenerMagic = wcsstr( strQueryString.QueryStr(), MAGIC_TWEENER_STRING ); if ( pszTweenerMagic == NULL ) { return NO_ERROR; } // // The next part of the string should be the URL // hr = strTweenerUrl.CopyWToUTF8Unescaped( pszTweenerMagic + MAGIC_TWEENER_STRING_LEN ); if ( FAILED( hr ) ) { return hr; } strTweenerUrl.Unescape(); // // Start generating the response // hr = pResponse->SetHeaderByReference( HttpHeaderContentType, "text/html", 9 ); if ( FAILED( hr ) ) { return hr; } hr = pResponse->SetHeader( HttpHeaderLocation, strTweenerUrl.QueryStr(), (USHORT)strTweenerUrl.QueryCCH() ); if ( FAILED( hr ) ) { return hr; } // // Calculate the authenticate header. This is the query string with // in the tweener URL // pszAuthenticateHeader = strchr( strTweenerUrl.QueryStr(), '?' ); if ( pszAuthenticateHeader == NULL ) { return NO_ERROR; } else { pszAuthenticateHeader++; } hr = strAuthenticateHeader.Copy( "Passport1.4 " ); if ( FAILED( hr ) ) { return hr; } hr = strAuthenticateHeader.Append( pszAuthenticateHeader ); if ( FAILED( hr ) ) { return hr; } pszBizarre = strchr( strAuthenticateHeader.QueryStr(), '&' ); while ( pszBizarre != NULL ) { *pszBizarre = ','; pszBizarre++; pszBizarre = strchr( pszBizarre, '&' ); } hr = pResponse->SetHeader( "WWW-Authenticate", 16, strAuthenticateHeader.QueryStr(), (USHORT)strAuthenticateHeader.QueryCCH() ); if ( FAILED( hr ) ) { return hr; } // // Finally, if this client supports it, return a 401, else a 302 // pszAcceptAuth = pRequest->GetHeader( "Accept-Auth" ); if ( pszAcceptAuth != NULL ) { hr = strAcceptAuth.Copy( pszAcceptAuth ); if ( FAILED( hr ) ) { return hr; } _strupr( strAcceptAuth.QueryStr() ); if ( strstr( strAcceptAuth.QueryStr(), "PASSPORT1.4" ) != NULL ) { fSendRedirect = FALSE; } } if ( fSendRedirect ) { pResponse->SetStatus( HttpStatusRedirect ); } else { pResponse->SetStatus( HttpStatusUnauthorized, Http401BadLogon ); } *pfTweenerHandled = TRUE; return NO_ERROR; } HRESULT PASSPORT_USER_CONTEXT::Create( TOKEN_CACHE_ENTRY * pToken, STRU & strAuthUser, STRU & strRemoteUser ) /*++ Routine Description: Create a user context based off token Arguments: pToken - Cached token strAuthUser - AUTH_USER strRemoteUser - REMOTE_USER Return Value: HRESULT --*/ { HRESULT hr; if ( pToken == NULL ) { DBG_ASSERT( FALSE ); return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); } hr = _strAuthUser.Copy( strAuthUser ); if ( FAILED( hr ) ) { return hr; } hr = _strRemoteUser.Copy( strRemoteUser ); if ( FAILED( hr ) ) { return hr; } _pToken = pToken; return NO_ERROR; } HANDLE PASSPORT_USER_CONTEXT::QueryPrimaryToken( VOID ) /*++ Routine Description: Get primary token for this user Arguments: None Return Value: Token handle --*/ { DBG_ASSERT( _pToken != NULL ); return _pToken->QueryPrimaryToken(); } HANDLE PASSPORT_USER_CONTEXT::QueryImpersonationToken( VOID ) /*++ Routine Description: Get impersonation token for this user Arguments: None Return Value: Token handle --*/ { DBG_ASSERT( _pToken != NULL ); return _pToken->QueryImpersonationToken(); }