Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ paket-files/
# End of https://www.gitignore.io/api/aspnetcore

node_modules
**/wwwroot/dist
#**/wwwroot/dist

# nunit test results
TestResult.xml
Expand All @@ -85,3 +85,7 @@ bootstrap-datetimepicker-build.min.css
colorpicker.css
colorpicker.min.css
*.log
CCMWeb/appsettings.Development.json

logs
CCM.Web/appsettings.Development.json
3 changes: 3 additions & 0 deletions CCM.Core/Attributes/FilterPropertyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

namespace CCM.Core.Attributes
{
/// <summary>
/// Used to collect possible discovery filter properties, use as '[FilterProperty(TableName = "Locations", ColumnName = "Name")]'
/// </summary>
public class FilterPropertyAttribute : Attribute
{
public string TableName { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

namespace CCM.WebCommon.Authentication
namespace CCM.Core.Authentication
{
public class Credentials
public class AuthenticationCredentials
{
public string Username { get; set; }
public string Password { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,94 +25,80 @@
*/

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Principal;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Results;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NLog;

namespace CCM.WebCommon.Authentication
namespace CCM.Core.Authentication
{
/// <remarks>
/// Based on code from
/// http://www.asp.net/web-api/overview/security/authentication-filters
/// </remarks>>
public abstract class BasicAuthenticationAttributeBase : Attribute, IAuthenticationFilter
//public abstract class BasicAuthenticationAttributeBase : Attribute, IAuthenticationFilter
public abstract class BasicAuthenticationAttributeBase : AuthenticationHandler<AuthenticationSchemeOptions>
{
protected static readonly Logger log = LogManager.GetCurrentClassLogger();
public bool AllowMultiple => false;
public BasicAuthenticationAttributeBase(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{

}

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
var authReqHeader = (string)Request.Headers["Authorization"];

if (authorization == null)
if (!string.IsNullOrEmpty(authReqHeader))
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
log.Debug("No authentication header in request for {0}", request.RequestUri);
return;
log.Debug("No authentication header in request for {0}", new Uri(Request.GetDisplayUrl()).ToString());
return AuthenticateResult.Fail("Missing authorization header");
}

if (authorization.Scheme != AuthenticationSchemes.Basic.ToString())
if (authReqHeader != null && !authReqHeader.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
log.Debug("Not a Basic authentication header in request for {0}", request.RequestUri);
return;
log.Debug("Not a Basic authentication header in request for {0}", new Uri(Request.GetDisplayUrl()).ToString());
return AuthenticateResult.Fail("Not using basic authorization scheme");
}

if (string.IsNullOrEmpty(authorization.Parameter))
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
log.Debug("Missing authentication credentials in request for {0}", request.RequestUri);
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request, HttpStatusCode.BadRequest);
return;
}

Credentials credentials = BasicAuthenticationHelper.ParseCredentials(authorization.Parameter);

if (credentials == null)
AuthenticationCredentials authenticationCredentials = BasicAuthenticationHelper.ParseCredentials(authReqHeader);
if (authenticationCredentials == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
log.Debug("No username and password in request for {0}", request.RequestUri);
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request, HttpStatusCode.BadRequest);
return;
log.Debug("No username and password in request for {0}", new Uri(Request.GetDisplayUrl()).ToString());
return AuthenticateResult.Fail("Missing or invalid credentials");
}

try
{
IPrincipal principal = await AuthenticateAsync(credentials.Username, credentials.Password, cancellationToken);

if (principal == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
log.Debug("Invalid username ({0}) or password in request for {1}, Req From: {2}, Req Host: {3}", credentials.Username, request.RequestUri, request.Headers.From, request.Headers.Host);
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
return;
}
// Setting up some claims
// TODO: See if things are actually still forwarded
var claims = new[] {
new Claim(ClaimTypes.Role, "Discovery endpoint verified"),
new Claim(ClaimTypes.NameIdentifier, authenticationCredentials.Username),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
// Claims principal, an array of Claim Identities or Claims (Many authorities can say how you are)
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

// Authentication succeeded.
context.Principal = principal;
}
catch (Exception ex)
{
log.Error(ex, $"Error in BasicAuthenticationAttribute on request to {request.RequestUri}");
context.ErrorResult = new InternalServerErrorResult(request);
}
return AuthenticateResult.Success(ticket);
}

protected abstract Task<IPrincipal> AuthenticateAsync(string userName, string password, CancellationToken cancellationToken);

public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,28 @@
*/

using System;
using System.Diagnostics;
using System.Text;

namespace CCM.WebCommon.Authentication
namespace CCM.Core.Authentication
{
public static class BasicAuthenticationHelper
{
public static Credentials ParseCredentials(string authorizationString)
public static AuthenticationCredentials ParseCredentials(string authorizationString)
{
byte[] credentialBytes;
if (String.IsNullOrEmpty(authorizationString))
{
return null;
}

byte[] credentialBytes;
try
{
credentialBytes = Convert.FromBase64String(authorizationString);
}
catch (FormatException)
catch (Exception ex)
{
Debug.WriteLine(ex);
return null;
}

Expand Down Expand Up @@ -75,7 +81,7 @@ public static Credentials ParseCredentials(string authorizationString)
return null;
}

return new Credentials() { Username = credentials[0], Password = credentials[1] };
return new AuthenticationCredentials() { Username = credentials[0], Password = credentials[1] };
}
}
}
Loading