Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1455 lines
42 KiB

  1. using System;
  2. using System.IO;
  3. using System.Net;
  4. using System.Text;
  5. using System.Xml;
  6. using System.Xml.Serialization;
  7. using System.Security.Principal;
  8. using System.Security.Cryptography;
  9. using System.Runtime.InteropServices;
  10. using Microsoft.Win32;
  11. using UDDI;
  12. using UDDI.Diagnostics;
  13. namespace UDDI.API.Authentication
  14. {
  15. /// ********************************************************************
  16. /// public DiscardAuthToken
  17. /// --------------------------------------------------------------------
  18. /// <summary>
  19. /// Represents a discard_authToken message.
  20. /// </summary>
  21. /// ********************************************************************
  22. ///
  23. [XmlRootAttribute("discard_authToken", Namespace=UDDI.API.Constants.Namespace)]
  24. public class DiscardAuthToken : IMessage
  25. {
  26. //
  27. // Attribute: generic
  28. //
  29. private string generic;
  30. [XmlAttribute("generic")]
  31. public string Generic
  32. {
  33. get { return generic; }
  34. set { generic = value; }
  35. }
  36. //
  37. // Element: authInfo
  38. //
  39. [XmlElement("authInfo")]
  40. public string AuthInfo = "";
  41. }
  42. /// ********************************************************************
  43. /// public GetAuthToken
  44. /// --------------------------------------------------------------------
  45. /// <summary>
  46. /// Represents a get_authToken message.
  47. /// </summary>
  48. /// ********************************************************************
  49. ///
  50. [XmlRootAttribute("get_authToken", Namespace=UDDI.API.Constants.Namespace)]
  51. public class GetAuthToken : IMessage
  52. {
  53. //
  54. // Attribute: generic
  55. //
  56. private string generic;
  57. [XmlAttribute("generic")]
  58. public string Generic
  59. {
  60. get { return generic; }
  61. set { generic = value; }
  62. }
  63. //
  64. // Attribute: userID
  65. //
  66. [XmlAttribute("userID")]
  67. public string UserID = "";
  68. //
  69. // Attribute: cred
  70. //
  71. [XmlAttribute("cred")]
  72. public string Cred = "";
  73. }
  74. /// ********************************************************************
  75. /// public AuthToken
  76. /// --------------------------------------------------------------------
  77. /// <summary>
  78. /// Represents an authToken.
  79. /// </summary>
  80. /// ********************************************************************
  81. ///
  82. [XmlRootAttribute("authToken", Namespace=UDDI.API.Constants.Namespace)]
  83. public class AuthToken
  84. {
  85. //
  86. // Attribute: operator
  87. //
  88. [XmlAttribute("operator")]
  89. public string Operator = Config.GetString( "Operator" );
  90. //
  91. // Attribute: generic
  92. //
  93. [XmlAttribute("generic")]
  94. public string Generic = Constants.Version;
  95. //
  96. // Element: authInfo
  97. //
  98. [XmlElement("authInfo")]
  99. public string AuthInfo;
  100. }
  101. public interface IAuthenticator
  102. {
  103. bool GetAuthenticationInfo( string userid, string password, out string ticket );
  104. bool Authenticate( string strTicket, int timeWindow );
  105. }
  106. public class WindowsAuthenticator : IAuthenticator
  107. {
  108. public bool GetAuthenticationInfo( string userid, string password, out string ticket )
  109. {
  110. try
  111. {
  112. //
  113. // Verify that a userid and password were not specifed
  114. //
  115. Debug.Verify( Utility.StringEmpty( userid ) && Utility.StringEmpty( password ), "UDDI_ERROR_FATALERROR_USERIDANDPASSINWINAUTH" );
  116. //
  117. // This form of authentication requires the impersonation of the caller
  118. // on the current thread of activity.
  119. //
  120. //
  121. // The ticket will not be used for this form of authentication
  122. //
  123. ticket = "";
  124. //
  125. // Setup the user credentials so we can verify that the user is a publisher
  126. //
  127. IPrincipal principal = System.Threading.Thread.CurrentPrincipal;
  128. Context.User.SetRole( principal );
  129. }
  130. catch( Exception exception )
  131. {
  132. //
  133. // Log the real exception
  134. //
  135. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
  136. //
  137. // Preserve the error number if it was a UDDIException; but DO NOT preserve the error
  138. // message. We want the error message in the log, but not shown to the user for security
  139. // reasons.
  140. //
  141. UDDIException uddiException = exception as UDDIException;
  142. if( null != uddiException )
  143. {
  144. throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  145. }
  146. else
  147. {
  148. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  149. }
  150. }
  151. return true;
  152. }
  153. public bool Authenticate( string strTicket, int timeWindow )
  154. {
  155. try
  156. {
  157. //
  158. // TODO: Verify strTicket is empty
  159. //
  160. //
  161. // No timeout check is possible for Windows Authentication
  162. //
  163. IPrincipal principal = System.Threading.Thread.CurrentPrincipal;
  164. Context.User.SetRole( principal );
  165. }
  166. catch( Exception exception )
  167. {
  168. //
  169. // Log the real exception
  170. //
  171. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
  172. //
  173. // Preserve the error number if it was a UDDIException; but DO NOT preserve the error
  174. // message. We want the error message in the log, but not shown to the user for security
  175. // reasons.
  176. //
  177. UDDIException uddiException = exception as UDDIException;
  178. if( null != uddiException )
  179. {
  180. throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  181. }
  182. else
  183. {
  184. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  185. }
  186. }
  187. return true;
  188. }
  189. }
  190. public class UDDIAuthenticator : IAuthenticator
  191. {
  192. const string ResetKeyDateFormat = "MM/dd/yyyy HH:mm:ss";
  193. [DllImport("advapi32.dll", SetLastError=true)]
  194. public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
  195. int dwLogonType, int dwLogonProvider, out int phToken);
  196. private string DomainFromUserID( string userid )
  197. {
  198. return userid.Substring( 0, userid.IndexOf( '\\' ) );
  199. }
  200. private string BaseUserNameFromUserID( string userid )
  201. {
  202. return userid.Substring( userid.IndexOf( '\\' ) + 1 );
  203. }
  204. public bool GetAuthenticationInfo( string userid, string password, out string ticket )
  205. {
  206. try
  207. {
  208. Debug.VerifySetting( "Security.Key" );
  209. Debug.VerifySetting( "Security.IV" );
  210. ticket = null;
  211. //
  212. // TODO: Need to support UPN formed user names
  213. //
  214. //
  215. // TODO: Need to look at use of this call on Domain controllers
  216. //
  217. //
  218. // The user account must have Log On Locally permission on the local computer.
  219. // This permission is granted to all users on workstations and servers,
  220. // but only to administrators on domain controllers.
  221. //
  222. //
  223. // Check userid and password by logging in
  224. //
  225. int windowstoken; // The Windows NT user token.
  226. string baseName = BaseUserNameFromUserID( userid );
  227. string domain = DomainFromUserID( userid );
  228. bool loggedOn = LogonUser( baseName,// User name.
  229. domain, // Domain name.
  230. password, // Password.
  231. 3, // Logon type = LOGON32_LOGON_NETWORK
  232. 0, // Logon provider = LOGON32_PROVIDER_DEFAULT
  233. out windowstoken ); // The user token for the specified user is returned here.
  234. if( !loggedOn )
  235. {
  236. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, userid + " failed logon. Error code was " + Marshal.GetLastWin32Error().ToString() );
  237. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  238. }
  239. WindowsIdentity wi = new WindowsIdentity( new IntPtr( windowstoken ) );
  240. WindowsPrincipal principal = new WindowsPrincipal( wi );
  241. Context.User.SetRole( principal );
  242. //
  243. // Generate the ticket
  244. //
  245. MemoryStream strm = new MemoryStream();
  246. Context.User.Serialize( strm );
  247. //
  248. // Get a key and initialization vector.
  249. //
  250. byte[] key = null;
  251. byte[] iv = null;
  252. GetSecurityPair( out key, out iv );
  253. //
  254. // Encrypt the ticket information
  255. //
  256. MemoryStream strmEncrypted = new MemoryStream();
  257. EncryptData( strm, strmEncrypted, key, iv );
  258. ticket = Convert.ToBase64String( strmEncrypted.GetBuffer(), 0, (int) strmEncrypted.Length );
  259. #if DEBUG
  260. Debug.Write( SeverityType.Info, CategoryType.Soap, "Ticket Out:----------\n" + ticket );
  261. #endif
  262. }
  263. catch( Exception exception )
  264. {
  265. //
  266. // Log the real exception
  267. //
  268. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
  269. //
  270. // Preserve the error number if it was a UDDIException; but DO NOT preserve the error
  271. // message. We want the error message in the log, but not shown to the user for security
  272. // reasons.
  273. //
  274. UDDIException uddiException = exception as UDDIException;
  275. if( null != uddiException )
  276. {
  277. throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  278. }
  279. else
  280. {
  281. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  282. }
  283. }
  284. return true;
  285. }
  286. public bool Authenticate( string strTicket, int timeWindow )
  287. {
  288. try
  289. {
  290. Debug.VerifySetting( "Security.Key" );
  291. Debug.VerifySetting( "Security.IV" );
  292. Debug.Verify( null != strTicket && strTicket.Length > 0, "UDDI_ERROR_AUTHTOKENREQUIRED_NOTOKENPUBLISHATTEMPT", ErrorType.E_authTokenRequired );
  293. #if DEBUG
  294. Debug.Write( SeverityType.Info, CategoryType.Soap, "Ticket In:----------\n" + strTicket );
  295. #endif
  296. //
  297. // Get a key and initialization vector.
  298. //
  299. byte[] key = null;
  300. byte[] iv = null;
  301. GetSecurityPair( out key, out iv );
  302. //
  303. // If the ticket cannot be decoded or decrypted throw an E_authTokenRequired
  304. //
  305. MemoryStream strm = new MemoryStream();
  306. try
  307. {
  308. byte[] ticket = Convert.FromBase64String( strTicket );
  309. //
  310. // Decrypt the ticket into a stream
  311. //
  312. MemoryStream strmEncrypted = new MemoryStream( ticket );
  313. DecryptData( strmEncrypted, strm, key, iv );
  314. }
  315. catch( Exception )
  316. {
  317. throw new UDDIException( UDDI.ErrorType.E_authTokenRequired, "UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDOREXPIRED" );
  318. }
  319. //
  320. // Deserialize the stream into the user class
  321. // and setup the context's user information
  322. //
  323. XmlSerializer serializer = XmlSerializerManager.GetSerializer( typeof( UserInfo ) );
  324. Context.User = (UserInfo) serializer.Deserialize( strm );
  325. //
  326. // Check the age of the token
  327. //
  328. Context.User.CheckAge( timeWindow );
  329. }
  330. catch( Exception exception )
  331. {
  332. //
  333. // Log the real exception
  334. //
  335. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
  336. //
  337. // Preserve the error number if it was a UDDIException; but DO NOT preserve the error
  338. // message. We want the error message in the log, but not shown to the user for security
  339. // reasons.
  340. //
  341. UDDIException uddiException = exception as UDDIException;
  342. if( null != uddiException )
  343. {
  344. throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  345. }
  346. else
  347. {
  348. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  349. }
  350. }
  351. return true;
  352. }
  353. //
  354. // This method returns a security key and initialization vector to be used in decryption or encryption
  355. // algorithms. If the key has timed out, a new one is created. This method was added as part of the changes needed to remove our dependency on SQLAgent.
  356. //
  357. private void GetSecurityPair( out byte[] key, out byte[] iv )
  358. {
  359. //
  360. // Make sure we have the settings that we need.
  361. //
  362. Debug.VerifySetting( "Security.KeyAutoReset" );
  363. Debug.VerifySetting( "Security.KeyLastResetDate" );
  364. Debug.VerifySetting( "Security.KeyTimeout" );
  365. Debug.VerifySetting( "Security.Key" );
  366. Debug.VerifySetting( "Security.IV" );
  367. key = null;
  368. iv = null;
  369. //
  370. // If we aren't supposed to automatically generate keys, then we don't care if the key has expired, so
  371. // just use the current values.
  372. //
  373. if( 1 == Config.GetInt( "Security.KeyAutoReset" ) )
  374. {
  375. //
  376. // Since we are allowed to generate keys, see if we need to.
  377. //
  378. //
  379. // Get the current date
  380. //
  381. DateTime current = DateTime.Now;
  382. //
  383. // Get the last time the key was reset.
  384. //
  385. // DateTime lastReset = DateTime.Parse( Config.GetString( "Security.KeyLastResetDate" ) );
  386. //
  387. // 739955 - Make sure date is parsed in the same format it was written.
  388. //
  389. DateTime lastReset = UDDILastResetDate.Get();
  390. //
  391. // Get the timeout (days)
  392. //
  393. int timeOutDays = Config.GetInt( "Security.KeyTimeout" );
  394. //
  395. // Has the key expired?
  396. //
  397. DateTime expiration = lastReset.AddDays( timeOutDays );
  398. if( current > expiration )
  399. {
  400. try
  401. {
  402. //
  403. // Generate new security information.
  404. //
  405. SymmetricAlgorithm sa = SymmetricAlgorithm.Create();
  406. sa.GenerateKey();
  407. key = sa.Key;
  408. sa.GenerateIV();
  409. iv = sa.IV;
  410. //
  411. // Store the new key, initialization vector, and current time.
  412. //
  413. //
  414. // 739955 - Make sure date is parsed in the same format it was written.
  415. //
  416. UDDILastResetDate.Set( current );
  417. Config.SetString( "Security.Key", Convert.ToBase64String( key ) );
  418. Config.SetString( "Security.IV", Convert.ToBase64String( iv ) );
  419. //
  420. // Make sure our current configuration is reading these values. TODO AddSetting is not
  421. // public, so we have to Refresh, this is pretty expensive, but it should only happen
  422. // once a week (by default).
  423. //
  424. Config.Refresh();
  425. }
  426. catch( Exception exception )
  427. {
  428. //
  429. // Don't let any exceptions propogate here, we'll catch them
  430. // and just throw a generic one. We don't want to reveal too much
  431. // information if we don't have to.
  432. //
  433. key = null;
  434. iv = null;
  435. //
  436. // Log the real exception
  437. //
  438. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
  439. //
  440. // Preserve the error number if it was a UDDIException; but DO NOT preserve the error
  441. // message. We want the error message in the log, but not shown to the user for security
  442. // reasons.
  443. //
  444. UDDIException uddiException = exception as UDDIException;
  445. if( null != uddiException )
  446. {
  447. throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  448. }
  449. else
  450. {
  451. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  452. }
  453. }
  454. }
  455. }
  456. //
  457. // If we don't have a value for the pair at this point, use the original values.
  458. //
  459. if( key == null && iv == null )
  460. {
  461. key = Convert.FromBase64String( Config.GetString( "Security.Key" ) );
  462. iv = Convert.FromBase64String( Config.GetString( "Security.IV" ) );
  463. }
  464. }
  465. private static void EncryptData( Stream input, Stream output, byte[] key, byte[] iv )
  466. {
  467. byte[] buffer = new byte[100];
  468. long bytesRead = 0;
  469. long bytesTotal = input.Length;
  470. //
  471. // Creates the default implementation, which is RijndaelManaged (AES).
  472. //
  473. SymmetricAlgorithm sa = SymmetricAlgorithm.Create();
  474. CryptoStream encodingStream = new CryptoStream( output, sa.CreateEncryptor( key, iv ), CryptoStreamMode.Write );
  475. //
  476. // Encrypt the bytes in the buffer in 100 bytes at a time
  477. //
  478. while( bytesRead < bytesTotal )
  479. {
  480. int n = input.Read( buffer, 0, 100 );
  481. encodingStream.Write( buffer, 0, n );
  482. bytesRead = bytesRead + n;
  483. }
  484. encodingStream.FlushFinalBlock();
  485. input.Position = 0;
  486. output.Position = 0;
  487. }
  488. private static void DecryptData( Stream input, Stream output, byte[] key, byte[] iv )
  489. {
  490. byte[] buffer = new byte[100];
  491. long bytesRead = 0;
  492. long bytesTotal = input.Length;
  493. //
  494. // Creates the default implementation, which is RijndaelManaged.
  495. //
  496. SymmetricAlgorithm sa = SymmetricAlgorithm.Create();
  497. CryptoStream decodingStream = new CryptoStream( output, sa.CreateDecryptor( key, iv ), CryptoStreamMode.Write );
  498. //
  499. // Encrypt the bytes in the buffer in 100 bytes at a time
  500. //
  501. while( bytesRead < bytesTotal )
  502. {
  503. int n = input.Read( buffer, 0, 100 );
  504. decodingStream.Write( buffer, 0, n );
  505. bytesRead = bytesRead + n;
  506. }
  507. decodingStream.FlushFinalBlock();
  508. input.Position = 0;
  509. output.Position = 0;
  510. }
  511. }
  512. public class PassportAuthenticator : IAuthenticator
  513. {
  514. public bool GetAuthenticationInfo( string userid, string password, out string ticket )
  515. {
  516. try
  517. {
  518. //
  519. // TODO: Should we generate and use our own ticket?
  520. //
  521. PassportAuthenticationHelper helper = new PassportAuthenticationHelper();
  522. ticket = helper.AuthenticateUser( userid, password, false );
  523. Context.User.SetPublisherRole( userid );
  524. }
  525. catch( Exception exception )
  526. {
  527. //
  528. // Log the real exception
  529. //
  530. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
  531. //
  532. // Preserve the error number if it was a UDDIException; but DO NOT preserve the error
  533. // message. We want the error message in the log, but not shown to the user for security
  534. // reasons.
  535. //
  536. UDDIException uddiException = exception as UDDIException;
  537. if( null != uddiException )
  538. {
  539. throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  540. }
  541. else
  542. {
  543. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  544. }
  545. }
  546. return true;
  547. }
  548. public bool Authenticate( string strTicket, int timeWindow )
  549. {
  550. try
  551. {
  552. PassportAuthenticationHelper helper = new PassportAuthenticationHelper();
  553. helper.ValidateAuthInfo( strTicket, timeWindow );
  554. string userID;
  555. string email;
  556. helper.GetUserInfo( strTicket, out userID, out email );
  557. Context.User.SetPublisherRole( userID );
  558. Context.User.Email = email;
  559. }
  560. catch( Exception exception )
  561. {
  562. //
  563. // Log the real exception
  564. //
  565. Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
  566. //
  567. // Preserve the error number if it was a UDDIException; but DO NOT preserve the error
  568. // message. We want the error message in the log, but not shown to the user for security
  569. // reasons.
  570. //
  571. UDDIException uddiException = exception as UDDIException;
  572. if( null != uddiException )
  573. {
  574. throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  575. }
  576. else
  577. {
  578. throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
  579. }
  580. }
  581. return true;
  582. }
  583. }
  584. /// ****************************************************************
  585. /// class PassportAuthenticationHelper
  586. /// ----------------------------------------------------------------
  587. /// <summary>
  588. /// Methods for authenticating users against the Passport
  589. /// authentication site.
  590. /// </summary>
  591. /// ****************************************************************
  592. ///
  593. class PassportAuthenticationHelper
  594. {
  595. private const int PassportEmailValidated = 0x0001;
  596. //
  597. // SECURITY: This interval should be configurable
  598. //
  599. private readonly TimeSpan refreshInterval = TimeSpan.FromDays( 1 );
  600. private PassportLib.Manager passport = null;
  601. private XmlDocument clientXml = null;
  602. private int keyVersion = 0;
  603. /// ****************************************************************
  604. /// public Authenticator [constructor]
  605. /// ----------------------------------------------------------------
  606. /// <summary>
  607. /// Constructs a new authentication object.
  608. /// </summary>
  609. /// ****************************************************************
  610. ///
  611. public PassportAuthenticationHelper()
  612. {
  613. Debug.Enter();
  614. //
  615. // Retrieve Passport key version.
  616. //
  617. RegistryKey key = Registry.LocalMachine.OpenSubKey( @"Software\Microsoft\Passport" );
  618. try
  619. {
  620. keyVersion = Convert.ToInt32( key.GetValue( "CurrentKey", 1 ) );
  621. }
  622. finally
  623. {
  624. key.Close();
  625. }
  626. //
  627. // Create an instance of the Passport Manager for this object.
  628. //
  629. passport = new PassportLib.Manager();
  630. //
  631. // TODO: Need to centralize default values such as 100 for connection limit
  632. //
  633. //
  634. // Set the connection limit for the webclient.
  635. //
  636. ServicePointManager.DefaultConnectionLimit =
  637. Config.GetInt( "Passport.ConnectionLimit", 100 );
  638. Debug.Leave();
  639. }
  640. /// ****************************************************************
  641. /// public AuthenticateUser
  642. /// ----------------------------------------------------------------
  643. /// <summary>
  644. /// Authenticates a user by sending the specified userId and
  645. /// password to the appropriate Passport authentication site.
  646. /// </summary>
  647. /// ----------------------------------------------------------------
  648. /// <param name="userId">
  649. /// The full user id for the user to be verified. This must be
  650. /// of the form user@domain.
  651. /// </param>
  652. ///
  653. /// <param name="password">
  654. /// The password of the user to authenticate.
  655. /// </param>
  656. ///
  657. /// <param name="savePassword">
  658. /// Specifies whether the user should remain logged into the
  659. /// site.
  660. /// </param>
  661. /// ----------------------------------------------------------------
  662. /// <returns>
  663. /// Returns an authorization token, if successful.
  664. /// </returns>
  665. /// ----------------------------------------------------------------
  666. /// <remarks>
  667. /// An authorization token is simply a concatenation of the
  668. /// passport ticket and profile, separated by a semicolon.
  669. /// </remarks>
  670. /// ****************************************************************
  671. ///
  672. public string AuthenticateUser( string userId, string password, bool savePassword )
  673. {
  674. Debug.Enter();
  675. Debug.Verify( !Utility.StringEmpty( userId ), "UDDI_ERROR_FATALERROR_NULLUSERID" );
  676. Debug.Verify( !Utility.StringEmpty( password ), "UDDI_ERROR_FATALERROR_NULLPASSWORD" );
  677. Debug.VerifySetting( "Passport.SiteID" );
  678. Debug.VerifySetting( "Passport.ReturnURL" );
  679. string domain = "";
  680. string authUrl = "";
  681. string authInfo = "";
  682. try
  683. {
  684. //
  685. // The authentication URL depends on the user's domain. First parse off the
  686. // domain (i.e. user@domain) using the Passport manager.
  687. //
  688. try
  689. {
  690. domain = passport.DomainFromMemberName( userId );
  691. }
  692. catch( Exception )
  693. {
  694. Debug.OperatorMessage(
  695. SeverityType.FailAudit,
  696. CategoryType.Authorization,
  697. OperatorMessageType.InvalidUserId,
  698. "Invalid format for user ID. Must be in the form of user@domain: " + userId );
  699. throw new UDDIException(
  700. ErrorType.E_unknownUser,
  701. "UDDI_ERROR_UNKNOWNUSER_BADFORMAT" );
  702. }
  703. Debug.Write(
  704. SeverityType.Info,
  705. CategoryType.Authorization,
  706. "Got domain=" + domain + " for user=" + userId );
  707. //
  708. // Lookup the URL we should use to authenticate users from this
  709. // domain.
  710. //
  711. authUrl = GetLoginURL( domain );
  712. Debug.Write(
  713. SeverityType.Info,
  714. CategoryType.Authorization,
  715. "Obtained authorization URL=" + authUrl );
  716. //
  717. // Append the site id and return url to the authorization url.
  718. //
  719. authUrl += "?id=" + Config.GetInt( "Passport.SiteID" ).ToString()
  720. + "&ru=" + Config.GetString( "Passport.ReturnURL" ) + "&kv=" + keyVersion.ToString();
  721. //
  722. // Create the login message.
  723. //
  724. string loginMessage =
  725. "<LoginRequest>" +
  726. "<ClientInfo name=\"Client\" version=\"1.35\"/>" +
  727. "<User>" +
  728. "<SignInName>" + userId + "</SignInName>" +
  729. "<Password>" + password + "</Password>" +
  730. "<SavePassword>" + ( true == savePassword ? "true" : "false" ) + "</SavePassword>" +
  731. "</User>" +
  732. "</LoginRequest>";
  733. byte[] requestData = Encoding.ASCII.GetBytes( loginMessage );
  734. //
  735. // Prepare the web request.
  736. //
  737. WebRequest webRequest = WebRequest.Create( authUrl );
  738. string proxy = Config.GetString( "Proxy", null );
  739. if( !Utility.StringEmpty( proxy ) )
  740. webRequest.Proxy = new WebProxy( proxy, true );
  741. webRequest.Method = "POST";
  742. webRequest.ContentType = "text/xml";
  743. webRequest.ContentLength = requestData.Length;
  744. webRequest.Timeout = Config.GetInt( "Passport.Timeout", 30000 );
  745. Stream stream;
  746. stream = webRequest.GetRequestStream();
  747. stream.Write( requestData, 0, requestData.Length );
  748. stream.Close();
  749. //
  750. // Post the data to the server.
  751. //
  752. #if DEBUG
  753. Debug.Write(
  754. SeverityType.Info,
  755. CategoryType.Authorization,
  756. "Posting XML login message: " + loginMessage );
  757. #endif
  758. //
  759. // SECURITY: try/finally for managing the stream
  760. // and webrequest in cases of failure
  761. //
  762. WebResponse webResponse = webRequest.GetResponse();
  763. stream = webResponse.GetResponseStream();
  764. //
  765. // Retrieve the response.
  766. //
  767. StreamReader reader = new StreamReader( stream );
  768. string response = reader.ReadToEnd();
  769. reader.Close();
  770. stream.Close();
  771. webResponse.Close();
  772. //
  773. // Process the response. If the response data contains Success="true",
  774. // then the user has been authenticated
  775. //
  776. if( "true" != Utility.ParseDelimitedToken( "Success=\"", "\"", response ) )
  777. {
  778. //
  779. // SECURITY: FailAudit this login failure
  780. //
  781. Debug.Write(
  782. SeverityType.Info,
  783. CategoryType.Authorization,
  784. "Login failed for user " + userId + "; response did not include attribute Success=true.\r\n" + response );
  785. throw new UDDIException(
  786. ErrorType.E_unknownUser,
  787. "UDDI_ERROR_UNKNOWNUSER_FAILED" );
  788. }
  789. //
  790. // Retrieve the associated ticket and profile.
  791. //
  792. string redirectUrl = Utility.ParseDelimitedToken( "<Redirect>", "</Redirect>", response );
  793. string ticket = Utility.ParseDelimitedToken( "&amp;t=", "&amp;p=", redirectUrl );
  794. string profile = Utility.ParseDelimitedToken( "&amp;p=", null, redirectUrl );
  795. if( null == ticket || null == profile )
  796. {
  797. Debug.Write(
  798. SeverityType.FailAudit,
  799. CategoryType.Authorization,
  800. "Login failed for user " + userId + "; response did not include Success=true.\r\n" + response );
  801. throw new UDDIException(
  802. ErrorType.E_unknownUser,
  803. "UDDI_ERROR_UNKNOWNUSER_FAILED" );
  804. }
  805. //
  806. // Build the authInfo from the ticket and profile.
  807. //
  808. authInfo = ticket + ";" + profile;
  809. Debug.Write(
  810. SeverityType.PassAudit,
  811. CategoryType.Authorization,
  812. "User " + userId + " authenticated.\r\nauthInfo=" + authInfo );
  813. Debug.Leave();
  814. return authInfo;
  815. }
  816. catch( UDDIException )
  817. {
  818. throw;
  819. }
  820. catch( WebException e )
  821. {
  822. //
  823. // A web exception is thrown when the Passport server is
  824. // unavailable or returns an error message.
  825. //
  826. string message = "Error authenticating user\r\n\r\n";
  827. if( null != e.Response )
  828. {
  829. StreamReader reader = new StreamReader( e.Response.GetResponseStream(), Encoding.UTF8 );
  830. string response = reader.ReadToEnd();
  831. reader.Close();
  832. string errorCode = Utility.ParseDelimitedToken( "Error Code=\"", "\"", response );
  833. //
  834. // Check to make sure an error code was returned. If it was,
  835. // we can provide a little more fidelity in our error
  836. // reporting.
  837. //
  838. if( null != errorCode )
  839. message += "Passport error: " + ParsePassportErrorCode( errorCode ) + "\r\n\r\n";
  840. message += "Response stream:\r\n" + response + "\r\n\r\n";
  841. }
  842. else
  843. {
  844. message += "Passport error: unknown error communicating with Passport\r\n\r\n";
  845. }
  846. Debug.OperatorMessage(
  847. SeverityType.Error,
  848. CategoryType.Authorization,
  849. OperatorMessageType.PassportSiteUnavailable,
  850. message + e.ToString() );
  851. throw new UDDIException(
  852. ErrorType.E_unknownUser,
  853. "UDDI_ERROR_UNKNOWNUSER_AUTHENTICATIONFAILED" );
  854. }
  855. catch( Exception e )
  856. {
  857. //
  858. // General exception handling.
  859. //
  860. Debug.OperatorMessage(
  861. SeverityType.Error,
  862. CategoryType.Authorization,
  863. OperatorMessageType.PassportSiteUnavailable,
  864. "Error authenticating user.\r\n\r\n" + e.ToString() );
  865. throw new UDDIException(
  866. ErrorType.E_unknownUser,
  867. "UDDI_ERROR_UNKNOWNUSER_AUTHENTICATIONFAILED" );
  868. }
  869. }
  870. /// ****************************************************************
  871. /// public ValidateAuthInfo
  872. /// ----------------------------------------------------------------
  873. /// <summary>
  874. /// Validates an authorization token.
  875. /// </summary>
  876. /// ----------------------------------------------------------------
  877. /// <param name="authInfo">
  878. /// The authorization token.
  879. /// </param>
  880. ///
  881. /// <param name="timeWindow">
  882. /// Time in seconds since login after which an authorization
  883. /// token is considered expired.
  884. /// </param>
  885. /// ****************************************************************
  886. ///
  887. public void ValidateAuthInfo( string authInfo, int timeWindow )
  888. {
  889. Debug.Enter();
  890. Debug.Verify( null != authInfo, "UDDI_ERROR_FATALERROR_NULLAUTHINFO" );
  891. //
  892. // Parse the ticket and profile from the authInfo string.
  893. //
  894. int separator = authInfo.IndexOf( ";" );
  895. if( -1 == separator )
  896. {
  897. Debug.OperatorMessage(
  898. SeverityType.FailAudit,
  899. CategoryType.Authorization,
  900. OperatorMessageType.InvalidTicketFormat,
  901. "Invalid ticket format: " + authInfo );
  902. throw new UDDIException(
  903. ErrorType.E_authTokenRequired,
  904. "UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDOREXPIRED" );
  905. }
  906. string ticket = authInfo.Substring( 0, separator );
  907. string profile = authInfo.Substring( separator + 1 );
  908. //
  909. // Attempt to authenticate the ticket and profile.
  910. //
  911. try
  912. {
  913. passport.OnStartPageManual( ticket, null, null, null, null, null );
  914. if( false == passport.IsAuthenticated( timeWindow, false, false ) )
  915. {
  916. Debug.Write(
  917. SeverityType.Info,
  918. CategoryType.Authorization,
  919. "Authentication failed; ticket is invalid or has expired" );
  920. throw new UDDIException(
  921. ErrorType.E_authTokenExpired,
  922. "UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDOREXPIRED" );
  923. }
  924. Debug.Write(
  925. SeverityType.PassAudit,
  926. CategoryType.Authorization,
  927. "Authentication successful" );
  928. Debug.Leave();
  929. }
  930. catch( UDDIException )
  931. {
  932. throw;
  933. }
  934. catch( Exception e )
  935. {
  936. Debug.Write(
  937. SeverityType.Error,
  938. CategoryType.Authorization,
  939. "Authentication failed; " + e.Message );
  940. throw new UDDIException(
  941. ErrorType.E_unknownUser,
  942. "UDDI_ERROR_UNKNOWNUSER_AUTHENTICATIONFAILED" );
  943. }
  944. }
  945. /// ****************************************************************
  946. /// public GetUserInfo
  947. /// ----------------------------------------------------------------
  948. /// <summary>
  949. /// Gets the user information associated with a specified
  950. /// authorization token.
  951. /// </summary>
  952. /// ----------------------------------------------------------------
  953. /// <param name="authInfo">
  954. /// The authorization token.
  955. /// </param>
  956. ///
  957. /// <param name="puid">
  958. /// [out] The member id for the user. This is different than
  959. /// the user id which was used to login.
  960. /// </param>
  961. ///
  962. /// <param name="userEmail">
  963. /// [out] The user's preferred email address.
  964. /// </param>
  965. /// ****************************************************************
  966. ///
  967. public void GetUserInfo( string authInfo, out string puid, out string userEmail )
  968. {
  969. Debug.Enter();
  970. Debug.Verify( null != authInfo, "UDDI_ERROR_FATALERROR_NULLAUTHINFO" );
  971. //
  972. // Parse the ticket and profile from the authInfo string.
  973. //
  974. int separator = authInfo.IndexOf( ";" );
  975. if( -1 == separator )
  976. {
  977. Debug.OperatorMessage(
  978. SeverityType.FailAudit,
  979. CategoryType.Authorization,
  980. OperatorMessageType.InvalidTicketFormat,
  981. "Invalid ticket format: " + authInfo );
  982. throw new UDDIException(
  983. ErrorType.E_authTokenRequired,
  984. "UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDFORMAT" );
  985. }
  986. string ticket = authInfo.Substring( 0, separator );
  987. string profile = authInfo.Substring( separator + 1 );
  988. //
  989. // Get the user's preferred email. This could change over time
  990. // since the user could change this on the Passport site, so
  991. // we'll always get the most recent value from the user's profile.
  992. //
  993. userEmail = (string)GetPropertyFromProfile( ticket, profile, "PreferredEmail" );
  994. //
  995. // Get the user's PUID. Since the PUID is a 64-bit value, we'll need
  996. // to build this from the low and high 32-bit values (Passport doesn't
  997. // store it as a single 64-bit value).
  998. //
  999. /*
  1000. object idHigh = GetPropertyFromProfile( ticket, profile, "MemberIDHigh" );
  1001. Debug.Verify( null != idHigh, "Unable to retrieve Passport member ID." );
  1002. object idLow = GetPropertyFromProfile( ticket, profile, "MemberIDLow" );
  1003. Debug.Verify( null != idLow, "Unable to retrieve Passport member ID." );
  1004. puid = ((int)idHigh).ToString( "X8" ) + ((int)idLow).ToString( "X8" );
  1005. */
  1006. //
  1007. // they now provide a single property to get the PUID
  1008. //
  1009. puid = passport.HexPUID;
  1010. Debug.Leave();
  1011. }
  1012. /// ****************************************************************
  1013. /// private GetPropertyFromProfile
  1014. /// ----------------------------------------------------------------
  1015. /// <summary>
  1016. /// Retrieves a property from the given user profile.
  1017. /// </summary>
  1018. /// ----------------------------------------------------------------
  1019. /// <param name="ticket">
  1020. /// Ticket for the authenticated user.
  1021. /// </param>
  1022. ///
  1023. /// <param name="profile">
  1024. /// Profile for the authenticated user.
  1025. /// </param>
  1026. ///
  1027. /// <param name="propertyName">
  1028. /// The name of the property to retrieve.
  1029. /// </param>
  1030. /// ----------------------------------------------------------------
  1031. /// <returns>
  1032. /// The value of the property.
  1033. /// </returns>
  1034. /// ****************************************************************
  1035. ///
  1036. private object GetPropertyFromProfile( string ticket, string profile, string propertyName )
  1037. {
  1038. Debug.Assert( null != ticket, "ticket cannot be null" );
  1039. Debug.Assert( null != profile, "profile cannot be null" );
  1040. Debug.Assert( null != propertyName, "propertyName cannot be null" );
  1041. object val = null;
  1042. try
  1043. {
  1044. passport.OnStartPageManual( ticket, profile, null, null, null, null );
  1045. val = passport[ propertyName ];
  1046. Debug.Write(
  1047. SeverityType.Info,
  1048. CategoryType.Authorization,
  1049. "Current Property: " + propertyName + "\r\n" +
  1050. "Current Value: " + ((null!=val)?val:"(null)" ) + "\r\n" +
  1051. "MemberIDHigh: " + passport[ "MemberIDHigh" ] + "\r\n" +
  1052. "MemberIDLow: " + passport[ "MemberIDLow" ] + "\r\n" +
  1053. "PreferredEmail: " + passport[ "PreferredEmail" ] + "\r\n" +
  1054. "ProfileVersion: " + passport[ "ProfileVersion" ] + "\r\n"
  1055. );
  1056. return val;
  1057. }
  1058. catch( Exception )
  1059. {
  1060. throw new UDDIException(
  1061. ErrorType.E_fatalError,
  1062. "UDDI_ERROR_FATALERROR_ERRORRETRIEVINGPROFILEDATA",
  1063. propertyName
  1064. );
  1065. }
  1066. }
  1067. /// ****************************************************************
  1068. /// public GetLoginURL
  1069. /// ----------------------------------------------------------------
  1070. /// <summary>
  1071. /// Retrieves the XML Login URL for the given domain.
  1072. /// </summary>
  1073. /// ----------------------------------------------------------------
  1074. /// <param name="domain">
  1075. /// The domain name.
  1076. /// </param>
  1077. /// ----------------------------------------------------------------
  1078. /// <returns>
  1079. /// The XML Login URL.
  1080. /// </returns>
  1081. /// ****************************************************************
  1082. ///
  1083. public string GetLoginURL( string domain )
  1084. {
  1085. Debug.VerifySetting( "Passport.ClientXmlFile" );
  1086. string location = Config.GetString( "Passport.ClientXmlFile" );
  1087. //
  1088. // Make sure the Client.xml file exists and is loaded.
  1089. //
  1090. if( !File.Exists( location ) )
  1091. {
  1092. UpdateClientXml();
  1093. }
  1094. //
  1095. // Check to see if it is time for a periodic refresh
  1096. // of the Client.xml file.
  1097. //
  1098. TimeSpan age = DateTime.Now.Subtract( File.GetLastWriteTime( location ) );
  1099. if( age >= refreshInterval )
  1100. {
  1101. UpdateClientXml();
  1102. }
  1103. //
  1104. // If the Client.xml file is still not loaded, load it now.
  1105. //
  1106. if( null == clientXml )
  1107. {
  1108. clientXml = new XmlDocument();
  1109. clientXml.Load( location );
  1110. }
  1111. //
  1112. // Make sure the Client.xml hasn't expired.
  1113. //
  1114. DateTime validUntil = DateTime.Parse( clientXml.DocumentElement.Attributes.GetNamedItem( "ValidUntil" ).Value );
  1115. if( validUntil < DateTime.Now )
  1116. UpdateClientXml();
  1117. //
  1118. // Retrieve the XMLLogin element text for the specified domain.
  1119. //
  1120. if( clientXml.HasChildNodes )
  1121. {
  1122. XmlNode node = clientXml.SelectSingleNode( "//Domain[@Name=\"" + domain + "\"]/XMLLogin" );
  1123. if( null != node )
  1124. return node.InnerText;
  1125. //
  1126. // We couldn't find the specified domain, so ll try the default
  1127. // XML login URL.
  1128. //
  1129. node = clientXml.SelectSingleNode( "//Domain[@Name=\"default\"]/XMLLogin" );
  1130. if( null != node )
  1131. return node.InnerText;
  1132. }
  1133. //
  1134. // We were unable to find a login URL for the domain.
  1135. //
  1136. Debug.OperatorMessage(
  1137. SeverityType.FailAudit,
  1138. CategoryType.Authorization,
  1139. OperatorMessageType.UnknownLoginURL,
  1140. "Could not find login URL for user from domain '" + domain + "'" );
  1141. throw new UDDIException(
  1142. ErrorType.E_fatalError,
  1143. "UDDI_ERROR_FATALERROR_ERRORAUTHENTICATINGINDOMAIN",
  1144. domain);
  1145. }
  1146. /// ****************************************************************
  1147. /// public UpdateClientXml
  1148. /// ----------------------------------------------------------------
  1149. /// <summary>
  1150. /// Retrieves a fresh copy of the Client.xml file from
  1151. /// Passport.
  1152. /// </summary>
  1153. /// ****************************************************************
  1154. ///
  1155. public void UpdateClientXml()
  1156. {
  1157. Debug.VerifySetting( "Passport.ClientXmlURL" );
  1158. Debug.VerifySetting( "Passport.ClientXmlFile" );
  1159. string url = Config.GetString( "Passport.ClientXmlURL" );
  1160. try
  1161. {
  1162. WebRequest request = WebRequest.Create( url );
  1163. string proxy = Config.GetString( "Proxy", null );
  1164. if( !Utility.StringEmpty( proxy ) )
  1165. request.Proxy = new WebProxy( proxy, true );
  1166. request.Timeout = Config.GetInt( "Passport.Timeout", 30000 );
  1167. WebResponse response = request.GetResponse();
  1168. try
  1169. {
  1170. clientXml = new XmlDocument();
  1171. clientXml.Load( response.GetResponseStream() );
  1172. clientXml.Save( Config.GetString( "Passport.ClientXmlFile" ) );
  1173. }
  1174. finally
  1175. {
  1176. response.Close();
  1177. }
  1178. }
  1179. catch( Exception e )
  1180. {
  1181. Debug.OperatorMessage(
  1182. SeverityType.Error,
  1183. CategoryType.Authorization,
  1184. OperatorMessageType.CannotRetrieveClientXml,
  1185. "Error retrieving Client.xml from '" + url + "'\n\nDetails:\n" + e.ToString() );
  1186. throw new UDDIException(
  1187. ErrorType.E_fatalError,
  1188. "UDDI_ERROR_FATALERROR_ERRORCOMMUNICATINGWITHPASSPORT" );
  1189. }
  1190. }
  1191. /// ****************************************************************
  1192. /// private ParsePassportErrorCode [static]
  1193. /// ----------------------------------------------------------------
  1194. /// <summary>
  1195. /// Returns an appropriate message for the Passport error
  1196. /// code.
  1197. /// </summary>
  1198. /// ----------------------------------------------------------------
  1199. /// <param name="errorCode">
  1200. /// The error code returned by Passport.
  1201. /// </param>
  1202. /// ----------------------------------------------------------------
  1203. /// <returns>
  1204. /// The error message.
  1205. /// </returns>
  1206. /// ****************************************************************
  1207. ///
  1208. private static string ParsePassportErrorCode( string errorCode )
  1209. {
  1210. //
  1211. // Check for a general network error. This indicates the service
  1212. // is unavailable.
  1213. //
  1214. if( 'n' == errorCode[0] )
  1215. return "Service unavailable";
  1216. //
  1217. // Return an appropriate message for the error code.
  1218. //
  1219. switch( errorCode )
  1220. {
  1221. case "e1":
  1222. return "Missing member name and password";
  1223. case "e2":
  1224. return "Missing member name";
  1225. case "e3":
  1226. return "Missing password";
  1227. case "e5a":
  1228. return "Incorrect password for given member name";
  1229. case "e5b":
  1230. return "Member name does not exist";
  1231. case "e5d":
  1232. return "Member name incomplete";
  1233. case "e8":
  1234. return "Missing Passport site ID (configuration error)";
  1235. case "e8a":
  1236. return "Missing Passport return URL (configuration error)";
  1237. case "e6":
  1238. goto case "e9";
  1239. case "e9":
  1240. return "Member has a KIDS Passport that does not have parental consent";
  1241. case "e10":
  1242. return "Password lockout. Several incorrect password attempted for member in a short time duration";
  1243. case "e11":
  1244. return "Member name or domain exceed 64 characters or password exceeded 16 characters";
  1245. case "e13":
  1246. return "General XML parsing or validation failure";
  1247. case "e13a":
  1248. return "Login sent to wrong domain authority (see referral tag)";
  1249. case "e14":
  1250. return "Malformed request (missing LoginRequest element)";
  1251. case "g1":
  1252. return "Malformed request (missing ClientInfo element)";
  1253. case "p1":
  1254. return "Invalid version attribute specified in ClientInfo element";
  1255. default:
  1256. return "Unknown Passport error code '" + errorCode + "'.";
  1257. }
  1258. }
  1259. }
  1260. }