The Labs \ Source Viewer \ SSCLI \ System.Net \ AuthenticationState

  1. //------------------------------------------------------------------------------
  2. // <copyright file="_AuthenticationState.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. namespace System.Net
  16. {
  17.     using System.Collections;
  18.     using System.IO;
  19.     using System.Runtime.Serialization;
  20.     using System.Security;
  21.     using System.Security.Cryptography.X509Certificates;
  22.     using System.Security.Permissions;
  23.     using System.Text;
  24.     using System.Text.RegularExpressions;
  25.     using System.Threading;
  26.     using System.Globalization;
  27.     using System.Net.Security;
  28.    
  29.     /// <devdoc>
  30.     /// <para>Used by HttpWebRequest to syncronize and orchestrate authentication<para>
  31.     /// </devdoc>
  32.     internal class AuthenticationState
  33.     {
  34.        
  35.         // true if we already attempted pre-authentication regardless if it has been
  36.         // 1) possible,
  37.         // 2) succesfull or
  38.         // 3) unsuccessfull
  39.         private bool TriedPreAuth;
  40.        
  41.         internal Authorization Authorization;
  42.        
  43.         internal IAuthenticationModule Module;
  44.        
  45.         // used to request a special connection for NTLM
  46.         internal string UniqueGroupId;
  47.        
  48.         // used to distinguish proxy auth from server auth
  49.         private bool IsProxyAuth;
  50.        
  51.         // the Uri of the host we're authenticating (proxy/server)
  52.         // used to match entries in the CredentialCache
  53.         internal Uri ChallengedUri;
  54.         private string ChallengedSpn;
  55.        
  56.        
  57.         internal HttpResponseHeader AuthenticateHeader {
  58.             get { return IsProxyAuth ? HttpResponseHeader.ProxyAuthenticate : HttpResponseHeader.WwwAuthenticate; }
  59.         }
  60.         internal string AuthorizationHeader {
  61.             get { return IsProxyAuth ? HttpKnownHeaderNames.ProxyAuthorization : HttpKnownHeaderNames.Authorization; }
  62.         }
  63.         internal HttpStatusCode StatusCodeMatch {
  64.             get { return IsProxyAuth ? HttpStatusCode.ProxyAuthenticationRequired : HttpStatusCode.Unauthorized; }
  65.         }
  66.        
  67.         internal AuthenticationState(bool isProxyAuth)
  68.         {
  69.             IsProxyAuth = isProxyAuth;
  70.         }
  71.        
  72.         //
  73.         // we need to do this to handle proxies in the correct way before
  74.         // calling into the AuthenticationManager APIs
  75.         //
  76.         private void PrepareState(HttpWebRequest httpWebRequest)
  77.         {
  78.             Uri newUri = IsProxyAuth ? httpWebRequest.ServicePoint.InternalAddress : httpWebRequest.Address;
  79.            
  80.             if ((object)ChallengedUri != (object)newUri) {
  81.                 if ((object)ChallengedUri == null || (object)ChallengedUri.Scheme != (object)newUri.Scheme || ChallengedUri.Host != newUri.Host || ChallengedUri.Port != newUri.Port) {
  82.                     //
  83.                     // must be a new server/port/scheme for this auth state, can happen on a redirect
  84.                     //
  85.                     ChallengedSpn = null;
  86.                 }
  87.                 ChallengedUri = newUri;
  88.             }
  89.             httpWebRequest.CurrentAuthenticationState = this;
  90.         }
  91.         //
  92.         //
  93.         //
  94.         internal string GetComputeSpn(HttpWebRequest httpWebRequest)
  95.         {
  96.             if (ChallengedSpn != null)
  97.                 return ChallengedSpn;
  98.            
  99.             string spnKey = httpWebRequest.ChallengedUri.GetParts(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.SafeUnescaped);
  100.             string spn = AuthenticationManager.SpnDictionary.InternalGet(spnKey);
  101.             if (spn == null) {
  102.                 if (!IsProxyAuth && httpWebRequest.ServicePoint.InternalProxyServicePoint) {
  103.                     // Here the NT-Security folks need us to attempt a DNS lookup to figure out
  104.                     // the FQDN. only do the lookup for short names (no IP addresses or DNS names)
  105.                     //
  106.                     // Initialize a backup value
  107.                     spn = httpWebRequest.ChallengedUri.Host;
  108.                    
  109.                     if (httpWebRequest.ChallengedUri.HostNameType != UriHostNameType.IPv6 && httpWebRequest.ChallengedUri.HostNameType != UriHostNameType.IPv4 && spn.IndexOf('.') == -1) {
  110.                         try {
  111.                             spn = Dns.InternalGetHostByName(spn).HostName;
  112.                             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::GetComputeSpn() Dns returned host:" + ValidationHelper.ToString(spn));
  113.                         }
  114.                         catch (Exception exception) {
  115.                             if (NclUtilities.IsFatal(exception))
  116.                                 throw;
  117.                             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::GetComputeSpn() GetHostByName(host) failed:" + ValidationHelper.ToString(exception));
  118.                         }
  119.                     }
  120.                 }
  121.                 else {
  122.                    
  123.                     spn = httpWebRequest.ServicePoint.Hostname;
  124.                 }
  125.                 spn = "HTTP/" + spn;
  126.                 spnKey = httpWebRequest.ChallengedUri.GetParts(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped) + "/";
  127.                 AuthenticationManager.SpnDictionary.InternalSet(spnKey, spn);
  128.             }
  129.             return ChallengedSpn = spn;
  130.         }
  131.         //
  132.         internal void PreAuthIfNeeded(HttpWebRequest httpWebRequest, ICredentials authInfo)
  133.         {
  134.             //
  135.             // attempt to do preauth, if needed
  136.             //
  137.             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() TriedPreAuth:" + TriedPreAuth.ToString() + " authInfo:" + ValidationHelper.HashString(authInfo));
  138.             if (!TriedPreAuth) {
  139.                 TriedPreAuth = true;
  140.                 if (authInfo != null) {
  141.                     PrepareState(httpWebRequest);
  142.                     Authorization preauth = null;
  143.                     try {
  144.                         preauth = AuthenticationManager.PreAuthenticate(httpWebRequest, authInfo);
  145.                         GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() preauth:" + ValidationHelper.HashString(preauth));
  146.                         if (preauth != null && preauth.Message != null) {
  147.                             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() setting TriedPreAuth to Complete:" + preauth.Complete.ToString());
  148.                             UniqueGroupId = preauth.ConnectionGroupId;
  149.                             httpWebRequest.Headers.Set(AuthorizationHeader, preauth.Message);
  150.                         }
  151.                     }
  152.                     catch (Exception exception) {
  153.                         GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() PreAuthenticate() returned exception:" + exception.Message);
  154.                         ClearSession(httpWebRequest);
  155.                     }
  156.                     catch {
  157.                         GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() PreAuthenticate() returned exception: Non-CLS Compliant Exception");
  158.                         ClearSession(httpWebRequest);
  159.                     }
  160.                 }
  161.             }
  162.         }
  163.        
  164.         //
  165.         // attempts to authenticate the request:
  166.         // returns true only if it succesfully called into the AuthenticationManager
  167.         // and got back a valid Authorization and succesfully set the appropriate auth headers
  168.         //
  169.         internal bool AttemptAuthenticate(HttpWebRequest httpWebRequest, ICredentials authInfo)
  170.         {
  171.             //
  172.             // Check for previous authentication attempts or the presence of credentials
  173.             //
  174.             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " AuthorizationHeader:" + AuthorizationHeader.ToString());
  175.            
  176.             if (Authorization != null && Authorization.Complete) {
  177.                 GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() Authorization!=null Authorization.Complete:" + Authorization.Complete.ToString());
  178.                 if (IsProxyAuth) {
  179.                     GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() ProxyAuth cleaning up auth status");
  180.                     ClearAuthReq(httpWebRequest);
  181.                 }
  182.                 return false;
  183.             }
  184.            
  185.             if (authInfo == null) {
  186.                 GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() authInfo==null Authorization#" + ValidationHelper.HashString(Authorization));
  187.                 return false;
  188.             }
  189.            
  190.             string challenge = httpWebRequest.AuthHeader(AuthenticateHeader);
  191.            
  192.             if (challenge == null) {
  193.                 //
  194.                 // the server sent no challenge, but this might be the case
  195.                 // in which we're succeeding an authorization handshake to
  196.                 // a proxy while a handshake with the server is still in progress.
  197.                 // if the handshake with the proxy is complete and we actually have
  198.                 // a handshake with the server in progress we can send the authorization header for the server as well.
  199.                 //
  200.                 if (!IsProxyAuth && Authorization != null && httpWebRequest.ProxyAuthenticationState.Authorization != null) {
  201.                     httpWebRequest.Headers.Set(AuthorizationHeader, Authorization.Message);
  202.                 }
  203.                 GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() challenge==null Authorization#" + ValidationHelper.HashString(Authorization));
  204.                 return false;
  205.             }
  206.            
  207.             //
  208.             // if the AuthenticationManager throws on Authenticate,
  209.             // bubble up that Exception to the user
  210.             //
  211.             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() challenge:" + challenge);
  212.            
  213.             PrepareState(httpWebRequest);
  214.             try {
  215.                 Authorization = AuthenticationManager.Authenticate(challenge, httpWebRequest, authInfo);
  216.             }
  217.             catch (Exception exception) {
  218.                 Authorization = null;
  219.                 GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() PreAuthenticate() returned exception:" + exception.Message);
  220.                 ClearSession(httpWebRequest);
  221.                 throw;
  222.             }
  223.             catch {
  224.                 Authorization = null;
  225.                 GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::PreAuthIfNeeded() PreAuthenticate() returned exception: Non-CLS Compliant Exception");
  226.                 ClearSession(httpWebRequest);
  227.                 throw;
  228.             }
  229.            
  230.            
  231.             if (Authorization == null) {
  232.                 GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() Authorization==null");
  233.                 return false;
  234.             }
  235.             if (Authorization.Message == null) {
  236.                 GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() Authorization.Message==null");
  237.                 Authorization = null;
  238.                 return false;
  239.             }
  240.            
  241.             UniqueGroupId = Authorization.ConnectionGroupId;
  242.             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::AttemptAuthenticate() AuthorizationHeader:" + AuthorizationHeader + " blob: " + Authorization.Message.Length + "bytes Complete:" + Authorization.Complete.ToString());
  243.            
  244.             try {
  245.                 //
  246.                 // a "bad" module could try sending bad characters in the HTTP headers.
  247.                 // catch the exception from WebHeaderCollection.CheckBadChars()
  248.                 // fail the auth process
  249.                 // and return the exception to the user as InnerException
  250.                 //
  251.                 httpWebRequest.Headers.Set(AuthorizationHeader, Authorization.Message);
  252.             }
  253.             catch {
  254.                 Authorization = null;
  255.                 ClearSession(httpWebRequest);
  256.                 throw;
  257.             }
  258.            
  259.             return true;
  260.         }
  261.        
  262.         internal void ClearAuthReq(HttpWebRequest httpWebRequest)
  263.         {
  264.             //
  265.             // if we are authenticating and we're being redirected to
  266.             // another authentication space then remove the current
  267.             // authentication header
  268.             //
  269.             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::ClearAuthReq() httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " " + AuthorizationHeader.ToString() + ": " + ValidationHelper.ToString(httpWebRequest.Headers[AuthorizationHeader]));
  270.             TriedPreAuth = false;
  271.             Authorization = null;
  272.             UniqueGroupId = null;
  273.             httpWebRequest.Headers.Remove(AuthorizationHeader);
  274.         }
  275.        
  276.         //
  277.         // gives the IAuthenticationModule a chance to update its internal state.
  278.         // do any necessary cleanup and update the Complete status of the associated Authorization.
  279.         //
  280.         internal void Update(HttpWebRequest httpWebRequest)
  281.         {
  282.             //
  283.             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() httpWebRequest#" + ValidationHelper.HashString(httpWebRequest) + " Authorization#" + ValidationHelper.HashString(Authorization) + " ResponseStatusCode:" + httpWebRequest.ResponseStatusCode.ToString());
  284.            
  285.             if (Authorization != null) {
  286.                
  287.                 PrepareState(httpWebRequest);
  288.                
  289.                 ISessionAuthenticationModule myModule = Module as ISessionAuthenticationModule;
  290.                
  291.                 if (myModule != null) {
  292.                     //
  293.                     // the whole point here is to complete the Security Context. Sometimes, though,
  294.                     // a bad cgi script or a bad server, could miss sending back the final blob.
  295.                     // in this case we won't be able to complete the handshake, but we'll have to clean up anyway.
  296.                     //
  297.                     string challenge = httpWebRequest.AuthHeader(AuthenticateHeader);
  298.                     GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() Complete:" + Authorization.Complete.ToString() + " Module:" + ValidationHelper.ToString(Module) + " challenge:" + ValidationHelper.ToString(challenge));
  299.                    
  300.                     if (!IsProxyAuth && httpWebRequest.ResponseStatusCode == HttpStatusCode.ProxyAuthenticationRequired) {
  301.                         //
  302.                         // don't call Update on the module, since there's an ongoing
  303.                         // handshake and we don't need to update any state in such a case
  304.                         //
  305.                         GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() skipping call to " + myModule.ToString() + ".Update() since we need to reauthenticate with the proxy");
  306.                     }
  307.                     else {
  308.                         bool complete = true;
  309.                         try {
  310.                             complete = myModule.Update(challenge, httpWebRequest);
  311.                             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() " + myModule.ToString() + ".Update() returned complete:" + complete.ToString());
  312.                         }
  313.                         catch (Exception exception) {
  314.                             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() " + myModule.ToString() + ".Update() caught exception:" + exception.Message);
  315.                             ClearSession(httpWebRequest);
  316.                            
  317.                            
  318.                         }
  319.                         catch {
  320.                             GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() " + myModule.ToString() + ".Update() caught exception: Non-CLS Compliant Exception");
  321.                             ClearSession(httpWebRequest);
  322.                            
  323.                            
  324.                         }
  325.                        
  326.                         Authorization.SetComplete(complete);
  327.                     }
  328.                    
  329.                 }
  330.                
  331.                 //
  332.                 // If authentication was successful, create binding between
  333.                 // the request and the authorization for future preauthentication
  334.                 //
  335.                 if (Module != null && Authorization.Complete && Module.CanPreAuthenticate && httpWebRequest.ResponseStatusCode != StatusCodeMatch) {
  336.                     GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::Update() handshake is Complete calling BindModule()");
  337.                     AuthenticationManager.BindModule(ChallengedUri, Authorization, Module);
  338.                 }
  339.             }
  340.         }
  341.        
  342.         internal void ClearSession()
  343.         {
  344.         }
  345.        
  346.         internal void ClearSession(HttpWebRequest httpWebRequest)
  347.         {
  348.             PrepareState(httpWebRequest);
  349.             ISessionAuthenticationModule myModule = Module as ISessionAuthenticationModule;
  350.             Module = null;
  351.            
  352.             if (myModule != null) {
  353.                 try {
  354.                     myModule.ClearSession(httpWebRequest);
  355.                 }
  356.                 catch (Exception exception) {
  357.                     if (NclUtilities.IsFatal(exception))
  358.                         throw;
  359.                    
  360.                     GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::ClearSession() " + myModule.ToString() + ".Update() caught exception:" + exception.Message);
  361.                 }
  362.                 catch {
  363.                     GlobalLog.Print("AuthenticationState#" + ValidationHelper.HashString(this) + "::ClearSession() " + myModule.ToString() + ".Update() caught exception: Non-CLS Compliant Exception");
  364.                 }
  365.             }
  366.            
  367.         }
  368.        
  369.     }
  370. }

Developer Fusion