1
/****************************************************************************
3
| Copyright (c) 2007 Novell, Inc.
6
| This program is free software; you can redistribute it and/or
7
| modify it under the terms of version 2 of the GNU General Public License as
8
| published by the Free Software Foundation.
10
| This program is distributed in the hope that it will be useful,
11
| but WITHOUT ANY WARRANTY; without even the implied warranty of
12
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
| GNU General Public License for more details.
15
| You should have received a copy of the GNU General Public License
16
| along with this program; if not, contact Novell, Inc.
18
| To contact Novell about this file by physical or electronic mail,
19
| you may find current contact information at www.novell.com
21
| Author: Todd Throne - tthrone@novell.com
22
|***************************************************************************/
25
using System.Collections;
26
using System.Collections.Specialized;
27
using System.Configuration;
30
using System.Threading;
32
using System.Web.Services;
35
using Simias.Authentication;
37
using Simias.Security.Web.AuthenticationService;
40
using Simias.DomainServices;
42
namespace Simias.Security.Web
45
/// HttpModule used for Simias authentication
47
public sealed class AuthenticationModule : IHttpModule
52
/// Used to log messages.
54
private static readonly ISimiasLog log = SimiasLogManager.GetLogger( typeof( AuthenticationModule ) );
57
/// Session tag used to store session information.
59
private static readonly string sessionTag = "simias";
61
// Response header set by the Http Authentication Module
62
public readonly static string DomainIDHeader = "Domain-ID";
65
/// Characters that are trimmed from the beginning and ending of the line.
67
private readonly static char[] trimChars = new char[] { '\"', '/', '\\' };
70
/// Enabled if ssl required is set in the web.config.
72
private bool sslRequired = true;
77
private int sslPort = 443;
80
/// Handle to the store.
82
private Store store = null;
85
/// Contains services specified in the web.config that require no
86
/// authentication. These may include entire web services, http
87
/// handlers and individual web service methods.
89
private Hashtable unauthenticatedServices = new Hashtable();
92
/// Hashtable that contains all the local addresses that this
93
/// machine is known as.
95
private Hashtable localAddresses = new Hashtable();
98
/// Manager singleton.
100
private Service.Manager simiasManager = null;
106
/// Gets the store handle for the store.
108
/// NOTE: It seems that calling Store.GetStore() statically, in the
109
/// constructor or in the Init() call causes the Mac client to fail
110
/// because it cannot find the Flaim libraries.
112
private Store StoreReference
120
store = Store.GetStore();
129
/// Gets a reference to the simias manager.
131
private Service.Manager SimiasManager
137
if ( simiasManager == null )
139
simiasManager = Service.Manager.GetManager();
143
return simiasManager;
148
#region Private Methods
151
/// Gets the domain ID from the realm if it exists.
153
/// <param name="context">HttpContext object.</param>
154
/// <returns>The domain ID if found, otherwise a null is returned.</returns>
155
private string GetDomainIDFromRealm( HttpContext context )
157
string domainID = null;
159
// Check for an authorization header.
160
string[] encodedCredentials = context.Request.Headers.GetValues( "Authorization" );
161
if ( ( encodedCredentials != null ) && ( encodedCredentials[ 0 ] != null ) )
163
// Make sure we are dealing with "Basic" credentials
164
if ( encodedCredentials[ 0 ].StartsWith( "Basic " ) )
166
// The authHeader after the basic signature is encoded
167
string authHeader = encodedCredentials[ 0 ].Remove( 0, 6 );
168
byte[] credential = System.Convert.FromBase64String( authHeader );
169
string decodedCredential = System.Text.Encoding.Default.GetString( credential, 0, credential.Length );
171
// Clients that newed up a NetCredential object with a URL
172
// come though on the authorization line in the following format:
173
// http://domain:port/simias10/service.asmx\username:password
175
int index = decodedCredential.LastIndexOf( '\\' );
178
string tempDomainID = decodedCredential.Substring( 0, index );
179
if ( ( tempDomainID != null ) && ( StoreReference.GetDomain( tempDomainID ) != null ) )
181
domainID = tempDomainID;
191
/// Returns whether address is an address local to this machine.
193
/// <param name="address">The address to test.</param>
194
/// <returns>True if address is local, otherwise false is returned.</returns>
195
private bool IsLocalAddress( string address )
197
bool isLocal = false;
201
IPAddress hostAddress = IPAddress.Parse( address );
202
if ( IPAddress.IsLoopback( hostAddress ) || localAddresses.ContainsKey( address ) )
207
catch ( FormatException )
209
// The address is a DNS name not a dotted-quad address.
210
if ( ( String.Compare( address, "loopback", true ) == 0 ) ||
211
( String.Compare( address, "localhost", true ) == 0 ) ||
212
localAddresses.ContainsKey( address.ToLower() ) )
222
/// Occurs when ASP.NET acquires the current state (for example, session state)
223
/// associated with the current request.
225
/// <param name="source">The source of the event.</param>
226
/// <param name="eventArgs">An EventArgs that contains the event data.</param>
227
private void OnAcquireRequestState( Object source, EventArgs eventArgs )
229
// Get the context for the current request.
230
HttpContext context = HttpContext.Current;
231
if ( context.Session != null )
233
// See if the user has a session from a previous login.
234
Session simiasSession = context.Session[ sessionTag ] as Session;
235
if ( simiasSession != null )
237
context.User = simiasSession.User;
238
if ( context.User.Identity.IsAuthenticated )
240
// The user is authenticated, set it as the current principal on this thread.
241
log.Debug("RAMESH: AUTHENTICATED USER");
242
if( DomainAgent.blockedIPs != null)
244
string soapPath = context.Request.Headers[ "SOAPAction" ];
245
string soapMethod = ( soapPath != null ) ? Path.GetFileName( soapPath.Trim( trimChars ) ) : null;
246
if ( soapMethod == null )
248
log.Debug("Getting the web method");
249
// See if it was specified as a query parameter.
250
soapMethod = context.Request.QueryString[ "op" ];
252
// If there is no operation query parameter, then use the entire query
253
// string as an index. This will allow the exception file to use
254
// something like Simias.asmx:?WSDL to allow WSDL download without credentials.
255
if ( soapMethod == null )
257
soapMethod = context.Request.Url.Query;
261
log.Debug("The web method is: {0}", soapMethod);
262
if( soapMethod.IndexOf( "IsUpdateAvailable") == -1 && soapMethod.IndexOf("CheckForUpdate") == -1)
264
if( DomainAgent.blockedIPs.ContainsKey(context.Request.UserHostAddress))//foreach( string cookie in DomainAgent.blockedIPs )
266
log.Debug("The IP's match. {0}", context.Request.UserHostAddress);
267
if( soapMethod == null || (soapMethod.IndexOf("GetUpdateFiles") == -1 && soapMethod.IndexOf("Platform") == -1 && soapMethod.IndexOf("File") == -1))
269
// Block the request...
270
log.Debug("Block the request");
271
context.Response.StatusCode = 401;
272
context.Response.StatusDescription = "Unauthorized";
273
context.ApplicationInstance.CompleteRequest();
279
log.Debug("Removing blocked ip new. continue for now");
280
if( DomainAgent.blockedIPs.ContainsKey(context.Request.UserHostAddress) )
281
DomainAgent.blockedIPs.Remove(context.Request.UserHostAddress);
286
//log.Debug("Ramesh: blocked list is null");
287
Thread.CurrentPrincipal = context.User;
291
// The user is not authenticated on this session. See if there are
292
// credentials specified on the request.
293
VerifyPrincipalFromRequest( context );
298
// A simias session from a previous login does not exist. See if there
299
// are credentials specified on the request.
300
VerifyPrincipalFromRequest( context );
305
// There is no session setup. Authenticate every time.
306
VerifyPrincipalFromRequest( context );
311
/// Occurs when a security module has established the identity of the user.
313
/// <param name="source">The source of the event.</param>
314
/// <param name="eventArgs">An EventArgs that contains the event data.</param>
315
private void OnAuthenticateRequest( Object source, EventArgs eventArgs )
317
// There is no way to access session information from the OnAuthenticateRequest
318
// event unless we implement our own cookie/session management.
320
// We will handle authentication from our OnAcquireRequestState event instead
321
// so that we can take advantage of the SessionStateModule implementation.
323
// Verify that we are on a secure connection, if not redirect to https
324
HttpContext context = HttpContext.Current;
325
if ( ( IsLocalAddress( context.Request.UserHostAddress ) == false ) &&
326
( context.Request.IsSecureConnection == false ) &&
327
( sslRequired == true ) )
329
// Redirect over https
330
UriBuilder redirectedUri = new UriBuilder( context.Request.Url.ToString() );
331
redirectedUri.Scheme = "https";
332
redirectedUri.Port = sslPort;
334
//log.Debug( redirectedUri.Uri.ToString() );
336
// You must have an SSL certificate configured on your web server for this to work
337
context.Response.Redirect( redirectedUri.Uri.ToString() );
342
/// Occurs as the first event in the HTTP pipeline chain of execution when ASP.NET
343
/// responds to a request.
345
/// <param name="source">The source of the event.</param>
346
/// <param name="eventArgs">An EventArgs that contains the event data.</param>
347
private void OnBeginRequest( Object source, EventArgs eventArgs )
349
HttpApplication app = source as HttpApplication;
350
string physicalPath = app.Request.PhysicalPath;
352
if ( ( app.Request.Path.IndexOf( '\\' ) >= 0 ) ||
353
( Path.GetFullPath( physicalPath ) != physicalPath ) )
355
//log.Debug( "AuthenticationModule.OnBeginRequest - Security attack detected!!" );
356
throw new HttpException( 404, "Not Found" );
359
// See if the simias services have been started.
360
if ( !SimiasManager.ServiceStarted )
362
HttpResponse response = app.Context.Response;
363
response.StatusCode = 503;
364
response.StatusDescription = "The server is not ready.";
365
app.CompleteRequest();
370
/// Parses the web.config appSettings values for AuthNotRequired.
372
/// <param name="parseString">String that contains the service names.</param>
373
private void ParseAuthNotRequiredServices( string parseString )
375
string[] services = parseString.Split( new char[] { ',' } );
376
foreach ( string s in services )
378
// Add this web service or method to the table.
379
unauthenticatedServices.Add( s.Trim().ToLower(), null );
380
// log.Debug("Ramesh: UnAuth services: {0}", s.Trim().ToLower());
385
/// Tries to authenticate the current request if authorization headers are present.
387
/// <param name="context">HttpContext that represents the request.</param>
388
private void VerifyPrincipalFromRequest( HttpContext context )
390
// See if this request requires authentication.
391
string webService = Path.GetFileName( context.Request.FilePath );
392
if ( !unauthenticatedServices.ContainsKey( webService.ToLower() ) )
394
// See if this request method requires authentication.
395
string soapPath = context.Request.Headers[ "SOAPAction" ];
396
string soapMethod = ( soapPath != null ) ? Path.GetFileName( soapPath.Trim( trimChars ) ) : null;
397
if ( soapMethod == null )
399
// See if it was specified as a query parameter.
400
soapMethod = context.Request.QueryString[ "op" ];
402
// If there is no operation query parameter, then use the entire query
403
// string as an index. This will allow the exception file to use
404
// something like Simias.asmx:?WSDL to allow WSDL download without credentials.
405
if ( soapMethod == null )
407
soapMethod = context.Request.Url.Query;
410
log.Debug("In verify[rincipalfromrequest: soapmethod is {0}", soapMethod);
412
if ( ( soapMethod == null ) ||
413
!unauthenticatedServices.ContainsKey( String.Format( "{0}:{1}", webService, soapMethod ).ToLower() ) )
415
// Check if the domain ID was specified in the basic realm.
416
string domainID = GetDomainIDFromRealm( context );
417
if ( domainID == null )
419
// Get the Domain ID.
420
domainID = context.Request.Headers.Get( Http.DomainIDHeader );
421
if ( domainID == null )
423
if ( Store.IsEnterpriseServer )
425
// If this is an enterprise server use the default domain.
426
domainID = StoreReference.DefaultDomain;
428
else if ( IsLocalAddress( context.Request.UserHostAddress ) )
430
// If this address is loopback, set the local domain in the HTTP context.
431
domainID = StoreReference.LocalDomain;
436
// Try and authenticate the request.
437
if ( domainID != null )
439
if ( Http.GetMember( domainID, context ) != null )
441
// Set the session to never expire on the local web service.
442
if ( context.Session != null )
444
if ( domainID == StoreReference.LocalDomain )
446
// Set to a very long time.
447
context.Session.Timeout = 60 * 24 * 365;
451
// use the default session timeout
458
string realmID = ( Store.IsEnterpriseServer ) ? StoreReference.DefaultDomain : StoreReference.LocalDomain;
459
string realm = StoreReference.GetDomain( ( realmID != null ) ? realmID : StoreReference.LocalDomain ).Name;
460
context.Response.StatusCode = 401;
461
context.Response.StatusDescription = "Unauthorized";
462
context.Response.AddHeader( "WWW-Authenticate", String.Concat( "Basic realm=\"", realm, "\"" ) );
463
context.ApplicationInstance.CompleteRequest();
471
#region IHttpModule Members
474
/// Initializes a module and prepares it to handle requests.
476
/// <param name="app">An HttpApplication that provides access to the methods,
477
/// properties, and events common to all application objects within an ASP.NET
478
/// application </param>
479
public void Init( HttpApplication app )
481
// Register for the interesting events in the HTTP life-cycle.
482
app.BeginRequest += new EventHandler( OnBeginRequest );
483
app.AuthenticateRequest += new EventHandler( OnAuthenticateRequest );
484
app.AcquireRequestState += new EventHandler( OnAcquireRequestState );
486
// Get the application settings from the Simias.config.
487
string setting = Store.Config.Get( "Authentication", "SimiasRequireSSL" );
488
if ( setting != null )
490
if ( String.Compare( setting, "no", true ) == 0 )
496
// Get the ssl port setting.
497
setting = Store.Config.Get( "Authentication", "SimiasSSLPort" );
498
if ( setting != null )
500
sslPort = Convert.ToInt32( setting );
503
// Get the services that do not need authentication.
504
setting = Store.Config.Get( "Authentication", "SimiasAuthNotRequired" );
505
if ( setting != null )
507
ParseAuthNotRequiredServices( setting );
510
// Get all the addresses that this host is known by.
511
string[] addresses = MyDns.GetHostAddresses();
512
foreach( string s in addresses )
514
localAddresses[ s.ToLower() ] = null;
519
/// Disposes of the resources (other than memory) used by the module that
520
/// implements IHttpModule.
522
public void Dispose()