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

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Security.Principal;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using UDDI;
using UDDI.Diagnostics;
namespace UDDI.API.Authentication
{
/// ********************************************************************
/// public DiscardAuthToken
/// --------------------------------------------------------------------
/// <summary>
/// Represents a discard_authToken message.
/// </summary>
/// ********************************************************************
///
[XmlRootAttribute("discard_authToken", Namespace=UDDI.API.Constants.Namespace)]
public class DiscardAuthToken : IMessage
{
//
// Attribute: generic
//
private string generic;
[XmlAttribute("generic")]
public string Generic
{
get { return generic; }
set { generic = value; }
}
//
// Element: authInfo
//
[XmlElement("authInfo")]
public string AuthInfo = "";
}
/// ********************************************************************
/// public GetAuthToken
/// --------------------------------------------------------------------
/// <summary>
/// Represents a get_authToken message.
/// </summary>
/// ********************************************************************
///
[XmlRootAttribute("get_authToken", Namespace=UDDI.API.Constants.Namespace)]
public class GetAuthToken : IMessage
{
//
// Attribute: generic
//
private string generic;
[XmlAttribute("generic")]
public string Generic
{
get { return generic; }
set { generic = value; }
}
//
// Attribute: userID
//
[XmlAttribute("userID")]
public string UserID = "";
//
// Attribute: cred
//
[XmlAttribute("cred")]
public string Cred = "";
}
/// ********************************************************************
/// public AuthToken
/// --------------------------------------------------------------------
/// <summary>
/// Represents an authToken.
/// </summary>
/// ********************************************************************
///
[XmlRootAttribute("authToken", Namespace=UDDI.API.Constants.Namespace)]
public class AuthToken
{
//
// Attribute: operator
//
[XmlAttribute("operator")]
public string Operator = Config.GetString( "Operator" );
//
// Attribute: generic
//
[XmlAttribute("generic")]
public string Generic = Constants.Version;
//
// Element: authInfo
//
[XmlElement("authInfo")]
public string AuthInfo;
}
public interface IAuthenticator
{
bool GetAuthenticationInfo( string userid, string password, out string ticket );
bool Authenticate( string strTicket, int timeWindow );
}
public class WindowsAuthenticator : IAuthenticator
{
public bool GetAuthenticationInfo( string userid, string password, out string ticket )
{
try
{
//
// Verify that a userid and password were not specifed
//
Debug.Verify( Utility.StringEmpty( userid ) && Utility.StringEmpty( password ), "UDDI_ERROR_FATALERROR_USERIDANDPASSINWINAUTH" );
//
// This form of authentication requires the impersonation of the caller
// on the current thread of activity.
//
//
// The ticket will not be used for this form of authentication
//
ticket = "";
//
// Setup the user credentials so we can verify that the user is a publisher
//
IPrincipal principal = System.Threading.Thread.CurrentPrincipal;
Context.User.SetRole( principal );
}
catch( Exception exception )
{
//
// Log the real exception
//
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
//
// Preserve the error number if it was a UDDIException; but DO NOT preserve the error
// message. We want the error message in the log, but not shown to the user for security
// reasons.
//
UDDIException uddiException = exception as UDDIException;
if( null != uddiException )
{
throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
else
{
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
}
return true;
}
public bool Authenticate( string strTicket, int timeWindow )
{
try
{
//
// TODO: Verify strTicket is empty
//
//
// No timeout check is possible for Windows Authentication
//
IPrincipal principal = System.Threading.Thread.CurrentPrincipal;
Context.User.SetRole( principal );
}
catch( Exception exception )
{
//
// Log the real exception
//
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
//
// Preserve the error number if it was a UDDIException; but DO NOT preserve the error
// message. We want the error message in the log, but not shown to the user for security
// reasons.
//
UDDIException uddiException = exception as UDDIException;
if( null != uddiException )
{
throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
else
{
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
}
return true;
}
}
public class UDDIAuthenticator : IAuthenticator
{
const string ResetKeyDateFormat = "MM/dd/yyyy HH:mm:ss";
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out int phToken);
private string DomainFromUserID( string userid )
{
return userid.Substring( 0, userid.IndexOf( '\\' ) );
}
private string BaseUserNameFromUserID( string userid )
{
return userid.Substring( userid.IndexOf( '\\' ) + 1 );
}
public bool GetAuthenticationInfo( string userid, string password, out string ticket )
{
try
{
Debug.VerifySetting( "Security.Key" );
Debug.VerifySetting( "Security.IV" );
ticket = null;
//
// TODO: Need to support UPN formed user names
//
//
// TODO: Need to look at use of this call on Domain controllers
//
//
// The user account must have Log On Locally permission on the local computer.
// This permission is granted to all users on workstations and servers,
// but only to administrators on domain controllers.
//
//
// Check userid and password by logging in
//
int windowstoken; // The Windows NT user token.
string baseName = BaseUserNameFromUserID( userid );
string domain = DomainFromUserID( userid );
bool loggedOn = LogonUser( baseName,// User name.
domain, // Domain name.
password, // Password.
3, // Logon type = LOGON32_LOGON_NETWORK
0, // Logon provider = LOGON32_PROVIDER_DEFAULT
out windowstoken ); // The user token for the specified user is returned here.
if( !loggedOn )
{
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, userid + " failed logon. Error code was " + Marshal.GetLastWin32Error().ToString() );
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
WindowsIdentity wi = new WindowsIdentity( new IntPtr( windowstoken ) );
WindowsPrincipal principal = new WindowsPrincipal( wi );
Context.User.SetRole( principal );
//
// Generate the ticket
//
MemoryStream strm = new MemoryStream();
Context.User.Serialize( strm );
//
// Get a key and initialization vector.
//
byte[] key = null;
byte[] iv = null;
GetSecurityPair( out key, out iv );
//
// Encrypt the ticket information
//
MemoryStream strmEncrypted = new MemoryStream();
EncryptData( strm, strmEncrypted, key, iv );
ticket = Convert.ToBase64String( strmEncrypted.GetBuffer(), 0, (int) strmEncrypted.Length );
#if DEBUG
Debug.Write( SeverityType.Info, CategoryType.Soap, "Ticket Out:----------\n" + ticket );
#endif
}
catch( Exception exception )
{
//
// Log the real exception
//
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
//
// Preserve the error number if it was a UDDIException; but DO NOT preserve the error
// message. We want the error message in the log, but not shown to the user for security
// reasons.
//
UDDIException uddiException = exception as UDDIException;
if( null != uddiException )
{
throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
else
{
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
}
return true;
}
public bool Authenticate( string strTicket, int timeWindow )
{
try
{
Debug.VerifySetting( "Security.Key" );
Debug.VerifySetting( "Security.IV" );
Debug.Verify( null != strTicket && strTicket.Length > 0, "UDDI_ERROR_AUTHTOKENREQUIRED_NOTOKENPUBLISHATTEMPT", ErrorType.E_authTokenRequired );
#if DEBUG
Debug.Write( SeverityType.Info, CategoryType.Soap, "Ticket In:----------\n" + strTicket );
#endif
//
// Get a key and initialization vector.
//
byte[] key = null;
byte[] iv = null;
GetSecurityPair( out key, out iv );
//
// If the ticket cannot be decoded or decrypted throw an E_authTokenRequired
//
MemoryStream strm = new MemoryStream();
try
{
byte[] ticket = Convert.FromBase64String( strTicket );
//
// Decrypt the ticket into a stream
//
MemoryStream strmEncrypted = new MemoryStream( ticket );
DecryptData( strmEncrypted, strm, key, iv );
}
catch( Exception )
{
throw new UDDIException( UDDI.ErrorType.E_authTokenRequired, "UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDOREXPIRED" );
}
//
// Deserialize the stream into the user class
// and setup the context's user information
//
XmlSerializer serializer = XmlSerializerManager.GetSerializer( typeof( UserInfo ) );
Context.User = (UserInfo) serializer.Deserialize( strm );
//
// Check the age of the token
//
Context.User.CheckAge( timeWindow );
}
catch( Exception exception )
{
//
// Log the real exception
//
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
//
// Preserve the error number if it was a UDDIException; but DO NOT preserve the error
// message. We want the error message in the log, but not shown to the user for security
// reasons.
//
UDDIException uddiException = exception as UDDIException;
if( null != uddiException )
{
throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
else
{
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
}
return true;
}
//
// This method returns a security key and initialization vector to be used in decryption or encryption
// 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.
//
private void GetSecurityPair( out byte[] key, out byte[] iv )
{
//
// Make sure we have the settings that we need.
//
Debug.VerifySetting( "Security.KeyAutoReset" );
Debug.VerifySetting( "Security.KeyLastResetDate" );
Debug.VerifySetting( "Security.KeyTimeout" );
Debug.VerifySetting( "Security.Key" );
Debug.VerifySetting( "Security.IV" );
key = null;
iv = null;
//
// If we aren't supposed to automatically generate keys, then we don't care if the key has expired, so
// just use the current values.
//
if( 1 == Config.GetInt( "Security.KeyAutoReset" ) )
{
//
// Since we are allowed to generate keys, see if we need to.
//
//
// Get the current date
//
DateTime current = DateTime.Now;
//
// Get the last time the key was reset.
//
// DateTime lastReset = DateTime.Parse( Config.GetString( "Security.KeyLastResetDate" ) );
//
// 739955 - Make sure date is parsed in the same format it was written.
//
DateTime lastReset = UDDILastResetDate.Get();
//
// Get the timeout (days)
//
int timeOutDays = Config.GetInt( "Security.KeyTimeout" );
//
// Has the key expired?
//
DateTime expiration = lastReset.AddDays( timeOutDays );
if( current > expiration )
{
try
{
//
// Generate new security information.
//
SymmetricAlgorithm sa = SymmetricAlgorithm.Create();
sa.GenerateKey();
key = sa.Key;
sa.GenerateIV();
iv = sa.IV;
//
// Store the new key, initialization vector, and current time.
//
//
// 739955 - Make sure date is parsed in the same format it was written.
//
UDDILastResetDate.Set( current );
Config.SetString( "Security.Key", Convert.ToBase64String( key ) );
Config.SetString( "Security.IV", Convert.ToBase64String( iv ) );
//
// Make sure our current configuration is reading these values. TODO AddSetting is not
// public, so we have to Refresh, this is pretty expensive, but it should only happen
// once a week (by default).
//
Config.Refresh();
}
catch( Exception exception )
{
//
// Don't let any exceptions propogate here, we'll catch them
// and just throw a generic one. We don't want to reveal too much
// information if we don't have to.
//
key = null;
iv = null;
//
// Log the real exception
//
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
//
// Preserve the error number if it was a UDDIException; but DO NOT preserve the error
// message. We want the error message in the log, but not shown to the user for security
// reasons.
//
UDDIException uddiException = exception as UDDIException;
if( null != uddiException )
{
throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
else
{
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
}
}
}
//
// If we don't have a value for the pair at this point, use the original values.
//
if( key == null && iv == null )
{
key = Convert.FromBase64String( Config.GetString( "Security.Key" ) );
iv = Convert.FromBase64String( Config.GetString( "Security.IV" ) );
}
}
private static void EncryptData( Stream input, Stream output, byte[] key, byte[] iv )
{
byte[] buffer = new byte[100];
long bytesRead = 0;
long bytesTotal = input.Length;
//
// Creates the default implementation, which is RijndaelManaged (AES).
//
SymmetricAlgorithm sa = SymmetricAlgorithm.Create();
CryptoStream encodingStream = new CryptoStream( output, sa.CreateEncryptor( key, iv ), CryptoStreamMode.Write );
//
// Encrypt the bytes in the buffer in 100 bytes at a time
//
while( bytesRead < bytesTotal )
{
int n = input.Read( buffer, 0, 100 );
encodingStream.Write( buffer, 0, n );
bytesRead = bytesRead + n;
}
encodingStream.FlushFinalBlock();
input.Position = 0;
output.Position = 0;
}
private static void DecryptData( Stream input, Stream output, byte[] key, byte[] iv )
{
byte[] buffer = new byte[100];
long bytesRead = 0;
long bytesTotal = input.Length;
//
// Creates the default implementation, which is RijndaelManaged.
//
SymmetricAlgorithm sa = SymmetricAlgorithm.Create();
CryptoStream decodingStream = new CryptoStream( output, sa.CreateDecryptor( key, iv ), CryptoStreamMode.Write );
//
// Encrypt the bytes in the buffer in 100 bytes at a time
//
while( bytesRead < bytesTotal )
{
int n = input.Read( buffer, 0, 100 );
decodingStream.Write( buffer, 0, n );
bytesRead = bytesRead + n;
}
decodingStream.FlushFinalBlock();
input.Position = 0;
output.Position = 0;
}
}
public class PassportAuthenticator : IAuthenticator
{
public bool GetAuthenticationInfo( string userid, string password, out string ticket )
{
try
{
//
// TODO: Should we generate and use our own ticket?
//
PassportAuthenticationHelper helper = new PassportAuthenticationHelper();
ticket = helper.AuthenticateUser( userid, password, false );
Context.User.SetPublisherRole( userid );
}
catch( Exception exception )
{
//
// Log the real exception
//
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
//
// Preserve the error number if it was a UDDIException; but DO NOT preserve the error
// message. We want the error message in the log, but not shown to the user for security
// reasons.
//
UDDIException uddiException = exception as UDDIException;
if( null != uddiException )
{
throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
else
{
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
}
return true;
}
public bool Authenticate( string strTicket, int timeWindow )
{
try
{
PassportAuthenticationHelper helper = new PassportAuthenticationHelper();
helper.ValidateAuthInfo( strTicket, timeWindow );
string userID;
string email;
helper.GetUserInfo( strTicket, out userID, out email );
Context.User.SetPublisherRole( userID );
Context.User.Email = email;
}
catch( Exception exception )
{
//
// Log the real exception
//
Debug.Write( SeverityType.FailAudit, CategoryType.Authorization, exception.ToString() );
//
// Preserve the error number if it was a UDDIException; but DO NOT preserve the error
// message. We want the error message in the log, but not shown to the user for security
// reasons.
//
UDDIException uddiException = exception as UDDIException;
if( null != uddiException )
{
throw new UDDIException( uddiException.Number, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
else
{
throw new UDDIException( UDDI.ErrorType.E_fatalError, "UDDI_ERROR_FATALERROR_LOGINFAILED" );
}
}
return true;
}
}
/// ****************************************************************
/// class PassportAuthenticationHelper
/// ----------------------------------------------------------------
/// <summary>
/// Methods for authenticating users against the Passport
/// authentication site.
/// </summary>
/// ****************************************************************
///
class PassportAuthenticationHelper
{
private const int PassportEmailValidated = 0x0001;
//
// SECURITY: This interval should be configurable
//
private readonly TimeSpan refreshInterval = TimeSpan.FromDays( 1 );
private PassportLib.Manager passport = null;
private XmlDocument clientXml = null;
private int keyVersion = 0;
/// ****************************************************************
/// public Authenticator [constructor]
/// ----------------------------------------------------------------
/// <summary>
/// Constructs a new authentication object.
/// </summary>
/// ****************************************************************
///
public PassportAuthenticationHelper()
{
Debug.Enter();
//
// Retrieve Passport key version.
//
RegistryKey key = Registry.LocalMachine.OpenSubKey( @"Software\Microsoft\Passport" );
try
{
keyVersion = Convert.ToInt32( key.GetValue( "CurrentKey", 1 ) );
}
finally
{
key.Close();
}
//
// Create an instance of the Passport Manager for this object.
//
passport = new PassportLib.Manager();
//
// TODO: Need to centralize default values such as 100 for connection limit
//
//
// Set the connection limit for the webclient.
//
ServicePointManager.DefaultConnectionLimit =
Config.GetInt( "Passport.ConnectionLimit", 100 );
Debug.Leave();
}
/// ****************************************************************
/// public AuthenticateUser
/// ----------------------------------------------------------------
/// <summary>
/// Authenticates a user by sending the specified userId and
/// password to the appropriate Passport authentication site.
/// </summary>
/// ----------------------------------------------------------------
/// <param name="userId">
/// The full user id for the user to be verified. This must be
/// of the form user@domain.
/// </param>
///
/// <param name="password">
/// The password of the user to authenticate.
/// </param>
///
/// <param name="savePassword">
/// Specifies whether the user should remain logged into the
/// site.
/// </param>
/// ----------------------------------------------------------------
/// <returns>
/// Returns an authorization token, if successful.
/// </returns>
/// ----------------------------------------------------------------
/// <remarks>
/// An authorization token is simply a concatenation of the
/// passport ticket and profile, separated by a semicolon.
/// </remarks>
/// ****************************************************************
///
public string AuthenticateUser( string userId, string password, bool savePassword )
{
Debug.Enter();
Debug.Verify( !Utility.StringEmpty( userId ), "UDDI_ERROR_FATALERROR_NULLUSERID" );
Debug.Verify( !Utility.StringEmpty( password ), "UDDI_ERROR_FATALERROR_NULLPASSWORD" );
Debug.VerifySetting( "Passport.SiteID" );
Debug.VerifySetting( "Passport.ReturnURL" );
string domain = "";
string authUrl = "";
string authInfo = "";
try
{
//
// The authentication URL depends on the user's domain. First parse off the
// domain (i.e. user@domain) using the Passport manager.
//
try
{
domain = passport.DomainFromMemberName( userId );
}
catch( Exception )
{
Debug.OperatorMessage(
SeverityType.FailAudit,
CategoryType.Authorization,
OperatorMessageType.InvalidUserId,
"Invalid format for user ID. Must be in the form of user@domain: " + userId );
throw new UDDIException(
ErrorType.E_unknownUser,
"UDDI_ERROR_UNKNOWNUSER_BADFORMAT" );
}
Debug.Write(
SeverityType.Info,
CategoryType.Authorization,
"Got domain=" + domain + " for user=" + userId );
//
// Lookup the URL we should use to authenticate users from this
// domain.
//
authUrl = GetLoginURL( domain );
Debug.Write(
SeverityType.Info,
CategoryType.Authorization,
"Obtained authorization URL=" + authUrl );
//
// Append the site id and return url to the authorization url.
//
authUrl += "?id=" + Config.GetInt( "Passport.SiteID" ).ToString()
+ "&ru=" + Config.GetString( "Passport.ReturnURL" ) + "&kv=" + keyVersion.ToString();
//
// Create the login message.
//
string loginMessage =
"<LoginRequest>" +
"<ClientInfo name=\"Client\" version=\"1.35\"/>" +
"<User>" +
"<SignInName>" + userId + "</SignInName>" +
"<Password>" + password + "</Password>" +
"<SavePassword>" + ( true == savePassword ? "true" : "false" ) + "</SavePassword>" +
"</User>" +
"</LoginRequest>";
byte[] requestData = Encoding.ASCII.GetBytes( loginMessage );
//
// Prepare the web request.
//
WebRequest webRequest = WebRequest.Create( authUrl );
string proxy = Config.GetString( "Proxy", null );
if( !Utility.StringEmpty( proxy ) )
webRequest.Proxy = new WebProxy( proxy, true );
webRequest.Method = "POST";
webRequest.ContentType = "text/xml";
webRequest.ContentLength = requestData.Length;
webRequest.Timeout = Config.GetInt( "Passport.Timeout", 30000 );
Stream stream;
stream = webRequest.GetRequestStream();
stream.Write( requestData, 0, requestData.Length );
stream.Close();
//
// Post the data to the server.
//
#if DEBUG
Debug.Write(
SeverityType.Info,
CategoryType.Authorization,
"Posting XML login message: " + loginMessage );
#endif
//
// SECURITY: try/finally for managing the stream
// and webrequest in cases of failure
//
WebResponse webResponse = webRequest.GetResponse();
stream = webResponse.GetResponseStream();
//
// Retrieve the response.
//
StreamReader reader = new StreamReader( stream );
string response = reader.ReadToEnd();
reader.Close();
stream.Close();
webResponse.Close();
//
// Process the response. If the response data contains Success="true",
// then the user has been authenticated
//
if( "true" != Utility.ParseDelimitedToken( "Success=\"", "\"", response ) )
{
//
// SECURITY: FailAudit this login failure
//
Debug.Write(
SeverityType.Info,
CategoryType.Authorization,
"Login failed for user " + userId + "; response did not include attribute Success=true.\r\n" + response );
throw new UDDIException(
ErrorType.E_unknownUser,
"UDDI_ERROR_UNKNOWNUSER_FAILED" );
}
//
// Retrieve the associated ticket and profile.
//
string redirectUrl = Utility.ParseDelimitedToken( "<Redirect>", "</Redirect>", response );
string ticket = Utility.ParseDelimitedToken( "&amp;t=", "&amp;p=", redirectUrl );
string profile = Utility.ParseDelimitedToken( "&amp;p=", null, redirectUrl );
if( null == ticket || null == profile )
{
Debug.Write(
SeverityType.FailAudit,
CategoryType.Authorization,
"Login failed for user " + userId + "; response did not include Success=true.\r\n" + response );
throw new UDDIException(
ErrorType.E_unknownUser,
"UDDI_ERROR_UNKNOWNUSER_FAILED" );
}
//
// Build the authInfo from the ticket and profile.
//
authInfo = ticket + ";" + profile;
Debug.Write(
SeverityType.PassAudit,
CategoryType.Authorization,
"User " + userId + " authenticated.\r\nauthInfo=" + authInfo );
Debug.Leave();
return authInfo;
}
catch( UDDIException )
{
throw;
}
catch( WebException e )
{
//
// A web exception is thrown when the Passport server is
// unavailable or returns an error message.
//
string message = "Error authenticating user\r\n\r\n";
if( null != e.Response )
{
StreamReader reader = new StreamReader( e.Response.GetResponseStream(), Encoding.UTF8 );
string response = reader.ReadToEnd();
reader.Close();
string errorCode = Utility.ParseDelimitedToken( "Error Code=\"", "\"", response );
//
// Check to make sure an error code was returned. If it was,
// we can provide a little more fidelity in our error
// reporting.
//
if( null != errorCode )
message += "Passport error: " + ParsePassportErrorCode( errorCode ) + "\r\n\r\n";
message += "Response stream:\r\n" + response + "\r\n\r\n";
}
else
{
message += "Passport error: unknown error communicating with Passport\r\n\r\n";
}
Debug.OperatorMessage(
SeverityType.Error,
CategoryType.Authorization,
OperatorMessageType.PassportSiteUnavailable,
message + e.ToString() );
throw new UDDIException(
ErrorType.E_unknownUser,
"UDDI_ERROR_UNKNOWNUSER_AUTHENTICATIONFAILED" );
}
catch( Exception e )
{
//
// General exception handling.
//
Debug.OperatorMessage(
SeverityType.Error,
CategoryType.Authorization,
OperatorMessageType.PassportSiteUnavailable,
"Error authenticating user.\r\n\r\n" + e.ToString() );
throw new UDDIException(
ErrorType.E_unknownUser,
"UDDI_ERROR_UNKNOWNUSER_AUTHENTICATIONFAILED" );
}
}
/// ****************************************************************
/// public ValidateAuthInfo
/// ----------------------------------------------------------------
/// <summary>
/// Validates an authorization token.
/// </summary>
/// ----------------------------------------------------------------
/// <param name="authInfo">
/// The authorization token.
/// </param>
///
/// <param name="timeWindow">
/// Time in seconds since login after which an authorization
/// token is considered expired.
/// </param>
/// ****************************************************************
///
public void ValidateAuthInfo( string authInfo, int timeWindow )
{
Debug.Enter();
Debug.Verify( null != authInfo, "UDDI_ERROR_FATALERROR_NULLAUTHINFO" );
//
// Parse the ticket and profile from the authInfo string.
//
int separator = authInfo.IndexOf( ";" );
if( -1 == separator )
{
Debug.OperatorMessage(
SeverityType.FailAudit,
CategoryType.Authorization,
OperatorMessageType.InvalidTicketFormat,
"Invalid ticket format: " + authInfo );
throw new UDDIException(
ErrorType.E_authTokenRequired,
"UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDOREXPIRED" );
}
string ticket = authInfo.Substring( 0, separator );
string profile = authInfo.Substring( separator + 1 );
//
// Attempt to authenticate the ticket and profile.
//
try
{
passport.OnStartPageManual( ticket, null, null, null, null, null );
if( false == passport.IsAuthenticated( timeWindow, false, false ) )
{
Debug.Write(
SeverityType.Info,
CategoryType.Authorization,
"Authentication failed; ticket is invalid or has expired" );
throw new UDDIException(
ErrorType.E_authTokenExpired,
"UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDOREXPIRED" );
}
Debug.Write(
SeverityType.PassAudit,
CategoryType.Authorization,
"Authentication successful" );
Debug.Leave();
}
catch( UDDIException )
{
throw;
}
catch( Exception e )
{
Debug.Write(
SeverityType.Error,
CategoryType.Authorization,
"Authentication failed; " + e.Message );
throw new UDDIException(
ErrorType.E_unknownUser,
"UDDI_ERROR_UNKNOWNUSER_AUTHENTICATIONFAILED" );
}
}
/// ****************************************************************
/// public GetUserInfo
/// ----------------------------------------------------------------
/// <summary>
/// Gets the user information associated with a specified
/// authorization token.
/// </summary>
/// ----------------------------------------------------------------
/// <param name="authInfo">
/// The authorization token.
/// </param>
///
/// <param name="puid">
/// [out] The member id for the user. This is different than
/// the user id which was used to login.
/// </param>
///
/// <param name="userEmail">
/// [out] The user's preferred email address.
/// </param>
/// ****************************************************************
///
public void GetUserInfo( string authInfo, out string puid, out string userEmail )
{
Debug.Enter();
Debug.Verify( null != authInfo, "UDDI_ERROR_FATALERROR_NULLAUTHINFO" );
//
// Parse the ticket and profile from the authInfo string.
//
int separator = authInfo.IndexOf( ";" );
if( -1 == separator )
{
Debug.OperatorMessage(
SeverityType.FailAudit,
CategoryType.Authorization,
OperatorMessageType.InvalidTicketFormat,
"Invalid ticket format: " + authInfo );
throw new UDDIException(
ErrorType.E_authTokenRequired,
"UDDI_ERROR_AUTHTOKENREQUIRED_INVALIDFORMAT" );
}
string ticket = authInfo.Substring( 0, separator );
string profile = authInfo.Substring( separator + 1 );
//
// Get the user's preferred email. This could change over time
// since the user could change this on the Passport site, so
// we'll always get the most recent value from the user's profile.
//
userEmail = (string)GetPropertyFromProfile( ticket, profile, "PreferredEmail" );
//
// Get the user's PUID. Since the PUID is a 64-bit value, we'll need
// to build this from the low and high 32-bit values (Passport doesn't
// store it as a single 64-bit value).
//
/*
object idHigh = GetPropertyFromProfile( ticket, profile, "MemberIDHigh" );
Debug.Verify( null != idHigh, "Unable to retrieve Passport member ID." );
object idLow = GetPropertyFromProfile( ticket, profile, "MemberIDLow" );
Debug.Verify( null != idLow, "Unable to retrieve Passport member ID." );
puid = ((int)idHigh).ToString( "X8" ) + ((int)idLow).ToString( "X8" );
*/
//
// they now provide a single property to get the PUID
//
puid = passport.HexPUID;
Debug.Leave();
}
/// ****************************************************************
/// private GetPropertyFromProfile
/// ----------------------------------------------------------------
/// <summary>
/// Retrieves a property from the given user profile.
/// </summary>
/// ----------------------------------------------------------------
/// <param name="ticket">
/// Ticket for the authenticated user.
/// </param>
///
/// <param name="profile">
/// Profile for the authenticated user.
/// </param>
///
/// <param name="propertyName">
/// The name of the property to retrieve.
/// </param>
/// ----------------------------------------------------------------
/// <returns>
/// The value of the property.
/// </returns>
/// ****************************************************************
///
private object GetPropertyFromProfile( string ticket, string profile, string propertyName )
{
Debug.Assert( null != ticket, "ticket cannot be null" );
Debug.Assert( null != profile, "profile cannot be null" );
Debug.Assert( null != propertyName, "propertyName cannot be null" );
object val = null;
try
{
passport.OnStartPageManual( ticket, profile, null, null, null, null );
val = passport[ propertyName ];
Debug.Write(
SeverityType.Info,
CategoryType.Authorization,
"Current Property: " + propertyName + "\r\n" +
"Current Value: " + ((null!=val)?val:"(null)" ) + "\r\n" +
"MemberIDHigh: " + passport[ "MemberIDHigh" ] + "\r\n" +
"MemberIDLow: " + passport[ "MemberIDLow" ] + "\r\n" +
"PreferredEmail: " + passport[ "PreferredEmail" ] + "\r\n" +
"ProfileVersion: " + passport[ "ProfileVersion" ] + "\r\n"
);
return val;
}
catch( Exception )
{
throw new UDDIException(
ErrorType.E_fatalError,
"UDDI_ERROR_FATALERROR_ERRORRETRIEVINGPROFILEDATA",
propertyName
);
}
}
/// ****************************************************************
/// public GetLoginURL
/// ----------------------------------------------------------------
/// <summary>
/// Retrieves the XML Login URL for the given domain.
/// </summary>
/// ----------------------------------------------------------------
/// <param name="domain">
/// The domain name.
/// </param>
/// ----------------------------------------------------------------
/// <returns>
/// The XML Login URL.
/// </returns>
/// ****************************************************************
///
public string GetLoginURL( string domain )
{
Debug.VerifySetting( "Passport.ClientXmlFile" );
string location = Config.GetString( "Passport.ClientXmlFile" );
//
// Make sure the Client.xml file exists and is loaded.
//
if( !File.Exists( location ) )
{
UpdateClientXml();
}
//
// Check to see if it is time for a periodic refresh
// of the Client.xml file.
//
TimeSpan age = DateTime.Now.Subtract( File.GetLastWriteTime( location ) );
if( age >= refreshInterval )
{
UpdateClientXml();
}
//
// If the Client.xml file is still not loaded, load it now.
//
if( null == clientXml )
{
clientXml = new XmlDocument();
clientXml.Load( location );
}
//
// Make sure the Client.xml hasn't expired.
//
DateTime validUntil = DateTime.Parse( clientXml.DocumentElement.Attributes.GetNamedItem( "ValidUntil" ).Value );
if( validUntil < DateTime.Now )
UpdateClientXml();
//
// Retrieve the XMLLogin element text for the specified domain.
//
if( clientXml.HasChildNodes )
{
XmlNode node = clientXml.SelectSingleNode( "//Domain[@Name=\"" + domain + "\"]/XMLLogin" );
if( null != node )
return node.InnerText;
//
// We couldn't find the specified domain, so ll try the default
// XML login URL.
//
node = clientXml.SelectSingleNode( "//Domain[@Name=\"default\"]/XMLLogin" );
if( null != node )
return node.InnerText;
}
//
// We were unable to find a login URL for the domain.
//
Debug.OperatorMessage(
SeverityType.FailAudit,
CategoryType.Authorization,
OperatorMessageType.UnknownLoginURL,
"Could not find login URL for user from domain '" + domain + "'" );
throw new UDDIException(
ErrorType.E_fatalError,
"UDDI_ERROR_FATALERROR_ERRORAUTHENTICATINGINDOMAIN",
domain);
}
/// ****************************************************************
/// public UpdateClientXml
/// ----------------------------------------------------------------
/// <summary>
/// Retrieves a fresh copy of the Client.xml file from
/// Passport.
/// </summary>
/// ****************************************************************
///
public void UpdateClientXml()
{
Debug.VerifySetting( "Passport.ClientXmlURL" );
Debug.VerifySetting( "Passport.ClientXmlFile" );
string url = Config.GetString( "Passport.ClientXmlURL" );
try
{
WebRequest request = WebRequest.Create( url );
string proxy = Config.GetString( "Proxy", null );
if( !Utility.StringEmpty( proxy ) )
request.Proxy = new WebProxy( proxy, true );
request.Timeout = Config.GetInt( "Passport.Timeout", 30000 );
WebResponse response = request.GetResponse();
try
{
clientXml = new XmlDocument();
clientXml.Load( response.GetResponseStream() );
clientXml.Save( Config.GetString( "Passport.ClientXmlFile" ) );
}
finally
{
response.Close();
}
}
catch( Exception e )
{
Debug.OperatorMessage(
SeverityType.Error,
CategoryType.Authorization,
OperatorMessageType.CannotRetrieveClientXml,
"Error retrieving Client.xml from '" + url + "'\n\nDetails:\n" + e.ToString() );
throw new UDDIException(
ErrorType.E_fatalError,
"UDDI_ERROR_FATALERROR_ERRORCOMMUNICATINGWITHPASSPORT" );
}
}
/// ****************************************************************
/// private ParsePassportErrorCode [static]
/// ----------------------------------------------------------------
/// <summary>
/// Returns an appropriate message for the Passport error
/// code.
/// </summary>
/// ----------------------------------------------------------------
/// <param name="errorCode">
/// The error code returned by Passport.
/// </param>
/// ----------------------------------------------------------------
/// <returns>
/// The error message.
/// </returns>
/// ****************************************************************
///
private static string ParsePassportErrorCode( string errorCode )
{
//
// Check for a general network error. This indicates the service
// is unavailable.
//
if( 'n' == errorCode[0] )
return "Service unavailable";
//
// Return an appropriate message for the error code.
//
switch( errorCode )
{
case "e1":
return "Missing member name and password";
case "e2":
return "Missing member name";
case "e3":
return "Missing password";
case "e5a":
return "Incorrect password for given member name";
case "e5b":
return "Member name does not exist";
case "e5d":
return "Member name incomplete";
case "e8":
return "Missing Passport site ID (configuration error)";
case "e8a":
return "Missing Passport return URL (configuration error)";
case "e6":
goto case "e9";
case "e9":
return "Member has a KIDS Passport that does not have parental consent";
case "e10":
return "Password lockout. Several incorrect password attempted for member in a short time duration";
case "e11":
return "Member name or domain exceed 64 characters or password exceeded 16 characters";
case "e13":
return "General XML parsing or validation failure";
case "e13a":
return "Login sent to wrong domain authority (see referral tag)";
case "e14":
return "Malformed request (missing LoginRequest element)";
case "g1":
return "Malformed request (missing ClientInfo element)";
case "p1":
return "Invalid version attribute specified in ClientInfo element";
default:
return "Unknown Passport error code '" + errorCode + "'.";
}
}
}
}