Skip to content

Commit

Permalink
Merge pull request #91 from dylanbeattie/master
Browse files Browse the repository at this point in the history
Fixed bug whereby the presence of the Basic authorizer contributor wo…
  • Loading branch information
holytshirt committed Dec 10, 2015
2 parents 2bc3092 + 3a04ec8 commit 30a942b
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 117 deletions.
1 change: 1 addition & 0 deletions src/OpenRasta.Tests.Unit/OpenRasta.Tests.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
<Compile Include="Authentication\DigestCredentialsReader_Specification.cs" />
<Compile Include="Pipeline\PipelineRunner_Specification.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Security\RequiresBasicAuthenticationInterceptor_Specification.cs" />
<Compile Include="Security\RequiresAuthenticationInterceptor_Specification.cs" />
<Compile Include="Security\RequiresRoleInterceptor_Specification.cs" />
<Compile Include="Text\Rfc2047Encoding_Specification.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,61 +36,5 @@ public void BasicAuthorizer_Succeeds_For_Valid_Credentials(string username, stri
result.ShouldBe(PipelineContinuation.Continue);
Context.User.Identity.Name.ShouldBe(username);
}

[Test]
public void BasicAuthorizer_Returns_Authenticate_Header_For_Invalid_Credentials()
{
var mockAuthenticationProvider = new Mock<IAuthenticationProvider>();
mockAuthenticationProvider.Setup(ap => ap.GetByUsername(It.IsAny<string>())).Returns(new Credentials());
mockAuthenticationProvider.Setup(ap => ap.ValidatePassword(It.IsAny<Credentials>(), It.IsAny<string>())).Returns(false);
given_dependency(mockAuthenticationProvider.Object);
given_pipeline_contributor<BasicAuthorizerContributor>();
Context.Request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password")));
var result = when_sending_notification<KnownStages.IHandlerSelection>();
result.ShouldBe(PipelineContinuation.RenderNow);
Context.OperationResult.ShouldBeOfType<OperationResult.Unauthorized>();
Context.User.ShouldBe(null);
}

[Test]
[TestCase("Basic THIS_IS_NOT_A_BASE64_ENCODED_CREDENTIAL")]
[TestCase("Invalid Header")]
public void BasicAuthorizer_Returns_Unauthorized_When_Header_Is_Invalid(string header)
{
var mockAuthenticationProvider = new Mock<IAuthenticationProvider>();
given_dependency(mockAuthenticationProvider.Object);
given_pipeline_contributor<BasicAuthorizerContributor>();

Context.Request.Headers.Add("Authorization", header);

var result = when_sending_notification<KnownStages.IHandlerSelection>();
result.ShouldBe(PipelineContinuation.RenderNow);
Context.OperationResult.ShouldBeOfType<OperationResult.Unauthorized>();
}

[Test]
public void BasicAuthorizer_Sends_WwwAuthenticate_For_Missing_Credentials()
{
var mockAuthenticationProvider = new Mock<IAuthenticationProvider>();
given_dependency(mockAuthenticationProvider.Object);
given_pipeline_contributor<BasicAuthorizerContributor>();

when_sending_notification<KnownStages.IHandlerSelection>();
when_sending_notification<KnownStages.IOperationResultInvocation>();
var header = String.Format("Basic realm=\"{0}\"", BasicAuthenticationRequiredHeader.DEFAULT_REALM);
Context.Response.Headers["WWW-Authenticate"].ShouldBe(header);
}

[Test]
public void BasicAuthorizer_Returns_NotAuthorized_For_Missing_Credentials()
{
var mockAuthenticationProvider = new Mock<IAuthenticationProvider>();
given_dependency(mockAuthenticationProvider.Object);
given_pipeline_contributor<BasicAuthorizerContributor>();

var result = when_sending_notification<KnownStages.IHandlerSelection>();
result.ShouldBe(PipelineContinuation.RenderNow);
Context.OperationResult.ShouldBeOfType<OperationResult.Unauthorized>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ public void execution_is_allowed()
.ShouldBeTrue();
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Diagnostics;
using System.Security.Principal;
using Moq;
using NUnit.Framework;
using OpenRasta.Hosting.InMemory;
using OpenRasta.OperationModel;
using OpenRasta.Security;
using OpenRasta.Testing;
using OpenRasta.Web;

namespace RequiresBasicAuthenticationInterceptor_Specification
{
public class when_the_user_is_not_authenticated : context
{
[Test]
public void execution_is_denied()
{
var context = new InMemoryCommunicationContext();
const string REALM = "Test Realm";
var authenticationInterceptor = new RequiresBasicAuthenticationInterceptor(context, REALM);
authenticationInterceptor.BeforeExecute(new Mock<IOperation>().Object).ShouldBeFalse();
context.OperationResult.ShouldBeOfType<OperationResult.Unauthorized>();
var expectedHeader = String.Format("Basic realm=\"{0}\"", REALM);
context.Response.Headers["WWW-Authenticate"].ShouldBe(expectedHeader);
}
}
}
2 changes: 2 additions & 0 deletions src/OpenRasta/OpenRasta.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@
<Compile Include="Security\DigestHeader.cs" />
<Compile Include="Security\IAuthenticationProvider.cs" />
<Compile Include="Security\PrincipalAuthorizationAttribute.cs" />
<Compile Include="Security\RequiresBasicAuthenticationInterceptor.cs" />
<Compile Include="Security\RequiresBasicAuthenticationAttribute.cs" />
<Compile Include="Security\RequiresAuthenticationAttribute.cs" />
<Compile Include="Security\RequiresAuthenticationInterceptor.cs" />
<Compile Include="Security\RequiresRoleAttribute.cs" />
Expand Down
69 changes: 17 additions & 52 deletions src/OpenRasta/Pipeline/Contributors/BasicAuthorizerContributor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ namespace OpenRasta.Pipeline.Contributors
{
public class BasicAuthorizerContributor : IPipelineContributor
{
public const string REALM = "Basic Authentication";

private readonly IDependencyResolver _resolver;
private IAuthenticationProvider _authentication;

Expand All @@ -43,72 +41,39 @@ public void Initialize(IPipeline pipelineRunner)
.After<KnownStages.IBegin>()
.And
.Before<KnownStages.IHandlerSelection>();

pipelineRunner.Notify(WriteCredentialRequest)
.After<KnownStages.IOperationResultInvocation>()
.And
.Before<KnownStages.IResponseCoding>();
}

public PipelineContinuation ReadCredentials(ICommunicationContext context)
{
if (!_resolver.HasDependency(typeof(IAuthenticationProvider)))
{
return NotAuthorized(context);
}

_authentication = _resolver.Resolve<IAuthenticationProvider>();

try
if (_resolver.HasDependency(typeof(IAuthenticationProvider)))
{
_authentication = _resolver.Resolve<IAuthenticationProvider>();
var header = ReadBasicAuthHeader(context);

if (header == null)
{
return NotAuthorized(context);
}

var credentials = _authentication.GetByUsername(header.Username);

if (credentials == null)
if (header != null)
{
return NotAuthorized(context);
}
var credentials = _authentication.GetByUsername(header.Username);

if (!_authentication.ValidatePassword(credentials, header.Password))
{
return NotAuthorized(context);
if (_authentication.ValidatePassword(credentials, header.Password))
{
IIdentity id = new GenericIdentity(credentials.Username, "Basic");
context.User = new GenericPrincipal(id, credentials.Roles);
}
}
IIdentity id = new GenericIdentity(credentials.Username, "Basic");
context.User = new GenericPrincipal(id, credentials.Roles);
return PipelineContinuation.Continue;
}
catch (ArgumentException ex)
{
return NotAuthorized(context);
}
}

private static BasicAuthorizationHeader ReadBasicAuthHeader(ICommunicationContext context)
{
var header = context.Request.Headers["Authorization"];
return string.IsNullOrEmpty(header) ? null : BasicAuthorizationHeader.Parse(header);
}

private static PipelineContinuation NotAuthorized(ICommunicationContext context)
{
context.OperationResult = new OperationResult.Unauthorized();
return PipelineContinuation.RenderNow;
return PipelineContinuation.Continue;
}

private static PipelineContinuation WriteCredentialRequest(ICommunicationContext context)
private static BasicAuthorizationHeader ReadBasicAuthHeader(ICommunicationContext context)
{
if (context.OperationResult is OperationResult.Unauthorized)
try
{
var header = context.Request.Headers["Authorization"];
return string.IsNullOrEmpty(header) ? null : BasicAuthorizationHeader.Parse(header);
} catch (ArgumentException ex)
{
var header = new BasicAuthenticationRequiredHeader().ServerResponseHeader;
context.Response.Headers["WWW-Authenticate"] = header;
return (null);
}
return PipelineContinuation.Continue;
}
}
}
Expand Down
10 changes: 3 additions & 7 deletions src/OpenRasta/Security/BasicAuthorizationHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,9 @@ private BasicAuthorizationHeader(string username, string password)
public static BasicAuthorizationHeader Parse(string header)
{
var tokens = header.Split(' ');
if (tokens.Length != 2)
if (tokens.Length != 2 || tokens[0] != "Basic")
{
throw (new ArgumentException("Supplied header is not in the format Basic {base64-encoded credential pair}", "header"));
}
if (tokens[0] != "Basic")
{
throw (new ArgumentException("Supplied header is not an HTTP Basic authorization header", header));
return (null);
}
try
{
Expand All @@ -46,7 +42,7 @@ public static BasicAuthorizationHeader Parse(string header)
return (new BasicAuthorizationHeader(credentials[0], credentials[1]));
} catch (FormatException ex)
{
throw (new ArgumentException("Supplied header doesn't contain valid base64 encoded credentials", ex));
return (null);
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/OpenRasta/Security/RequiresAuthenticationInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ public override bool BeforeExecute(IOperation operation)
{
if (_context.User == null || _context.User.Identity == null || !_context.User.Identity.IsAuthenticated)
{
_context.OperationResult = new OperationResult.Unauthorized();
DenyAuthorization(_context);
return false;
}

return true;
}

protected virtual void DenyAuthorization(ICommunicationContext context)
{
_context.OperationResult = new OperationResult.Unauthorized();
}
}


}
57 changes: 57 additions & 0 deletions src/OpenRasta/Security/RequiresBasicAuthenticationAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#region License
/* Authors:
* Sebastien Lambla (seb@serialseb.com)
* Copyright:
* (C) 2007-2009 Caffeine IT & naughtyProd Ltd (http://www.caffeine-it.com)
* License:
* This file is distributed under the terms of the MIT License found at the end of this file.
*/
#endregion

using System;
using System.Collections.Generic;
using OpenRasta.DI;
using OpenRasta.OperationModel;
using OpenRasta.OperationModel.Interceptors;
using OpenRasta.Web;

namespace OpenRasta.Security
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class RequiresBasicAuthenticationAttribute : InterceptorProviderAttribute
{
private readonly string realm;

public RequiresBasicAuthenticationAttribute(string realm)
{
this.realm = realm;
}

public override IEnumerable<IOperationInterceptor> GetInterceptors(IOperation operation)
{
return new[]
{
new RequiresBasicAuthenticationInterceptor(DependencyManager.GetService<ICommunicationContext>(),realm)
};
}
}
}

#region Full license
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#endregion
25 changes: 25 additions & 0 deletions src/OpenRasta/Security/RequiresBasicAuthenticationInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using OpenRasta.OperationModel;
using OpenRasta.OperationModel.Interceptors;
using OpenRasta.Pipeline;
using OpenRasta.Web;

namespace OpenRasta.Security
{
public class RequiresBasicAuthenticationInterceptor : RequiresAuthenticationInterceptor
{
private readonly string realm;

public RequiresBasicAuthenticationInterceptor(ICommunicationContext context, string realm)
: base(context)
{
this.realm = realm;
}

protected override void DenyAuthorization(ICommunicationContext context)
{
base.DenyAuthorization(context);
var header = new BasicAuthenticationRequiredHeader(realm).ServerResponseHeader;
context.Response.Headers["WWW-Authenticate"] = header;
}
}
}

0 comments on commit 30a942b

Please sign in to comment.