Skip to content

Commit

Permalink
[DI-1383] - Adds basic authentication. (#132)
Browse files Browse the repository at this point in the history
* Adds basic authentication.

* Trying to fix tests.

* code simplification and renames
  • Loading branch information
DavidJGapCR authored Aug 26, 2024
1 parent 32f4a8b commit 9f8c5a1
Show file tree
Hide file tree
Showing 21 changed files with 199 additions and 98 deletions.
6 changes: 3 additions & 3 deletions DataImport.Common.Tests/PowerShellPreprocessorServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public void Setup()
{
_powerShellPreprocessorOptions = new PowerShellPreprocessorOptions();

var oAuthRequestWrapper = A.Fake<IOAuthRequestWrapper>();
var oAuthRequestWrapper = A.Fake<IAuthRequestWrapper>();
A.CallTo(() => oAuthRequestWrapper.GetAccessCode(null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetBearerToken(null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetBearerToken(null, null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetToken(null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetToken(null, null, null)).WithAnyArguments().Returns("fake token");

var powerShellPreprocessSettings = new PowerShellPreprocessSettings { EncryptionKey = Guid.NewGuid().ToString() };
_service = new PowerShellPreprocessorService(powerShellPreprocessSettings, _powerShellPreprocessorOptions, oAuthRequestWrapper);
Expand Down
77 changes: 77 additions & 0 deletions DataImport.Common/Helpers/AuthRequestWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System;
using System.Net;
using System.Security.Authentication;
using DataImport.Models;
using RestSharp;
using static DataImport.Common.Encryption;

namespace DataImport.Common.Helpers
{
public abstract class AuthRequestWrapper
{
public virtual RestClientOptions GetOptions(Uri tokenUrl)
{
RestClientOptions options;

if (ScriptExtensions.IgnoresCertificateErrors())
{
#pragma warning disable S4830
options = new RestClientOptions(tokenUrl.GetLeftPart(UriPartial.Authority))
{
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
};
#pragma warning restore S4830
}
else
{
options = new RestClientOptions(tokenUrl.GetLeftPart(UriPartial.Authority));
}

return options;
}

public virtual string GetAccessCode(ApiServer apiServer, string encryptionKey)
{
var authUrl = new Uri(apiServer.AuthUrl);
var authClient = new RestClient(authUrl.GetLeftPart(UriPartial.Authority));

var accessCodeRequest = new RestRequest(authUrl.AbsolutePath, Method.Post);
var apiServerKey = !string.IsNullOrEmpty(encryptionKey)
? Decrypt(apiServer.Key, encryptionKey)
: apiServer.Key;
accessCodeRequest.AddParameter("Client_id", apiServerKey);
accessCodeRequest.AddParameter("Response_type", "code");

var accessCodeResponse = authClient.Execute<AccessCodeResponse>(accessCodeRequest);

if (accessCodeResponse.StatusCode != HttpStatusCode.OK)
throw new AuthenticationException("Unable to retrieve an authorization code. Error message: " +
accessCodeResponse.ErrorMessage);
if (accessCodeResponse.Data.Error != null)
throw new AuthenticationException(
"Unable to retrieve an authorization code. Please verify that your application key is correct. Alternately, the service address may not be correct: " +
authUrl);

return accessCodeResponse.Data.Code;
}

public virtual string GetToken(RestRequest tokenRequest, RestClient oauthClient)
{
var tokenResponse = oauthClient.Execute<BearerTokenResponse>(tokenRequest);
if (tokenResponse.StatusCode != HttpStatusCode.OK)
throw new AuthenticationException("Unable to retrieve an access token. Error message: " +
tokenResponse.ErrorMessage);

if (tokenResponse.Data.Error != null || tokenResponse.Data.TokenType != "bearer")
throw new AuthenticationException(
"Unable to retrieve an access token. Please verify that your application secret is correct.");

return tokenResponse.Data.AccessToken;
}
}
}
53 changes: 53 additions & 0 deletions DataImport.Common/Helpers/BasicAuthRequestWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System;
using System.Text;
using DataImport.Models;
using RestSharp;
using static DataImport.Common.Encryption;

namespace DataImport.Common.Helpers
{
public class BasicAuthRequestWrapper : AuthRequestWrapper, IAuthRequestWrapper
{
public string GetToken(ApiServer apiServer, string encryptionKey)
{
return GetToken(apiServer, encryptionKey, null);
}

public string GetToken(ApiServer apiServer, string encryptionKey, string accessCode)
{
var tokenUrl = new Uri(apiServer.TokenUrl);
RestClientOptions options = GetOptions(tokenUrl);

var authClient = new RestClient(options);

var tokenRequest = new RestRequest(tokenUrl.AbsolutePath, Method.Post);

var apiServerKey = !string.IsNullOrEmpty(encryptionKey)
? Decrypt(apiServer.Key, encryptionKey)
: apiServer.Key;
var apiServerSecret = !string.IsNullOrEmpty(encryptionKey)
? Decrypt(apiServer.Secret, encryptionKey)
: apiServer.Secret;

var keySecretBytes = Encoding.UTF8.GetBytes($"{apiServerKey}:{apiServerSecret}");
tokenRequest.AddHeader("Authorization", $"Basic {Convert.ToBase64String(keySecretBytes)}");

if (accessCode != null)
{
tokenRequest.AddParameter("code", accessCode);
tokenRequest.AddParameter("grant_type", "authorization_code");
}
else
{
tokenRequest.AddParameter("grant_type", "client_credentials");
}

return GetToken(tokenRequest, authClient);
}
}
}
18 changes: 18 additions & 0 deletions DataImport.Common/Helpers/IAuthRequestWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
// Licensed to the Ed-Fi Alliance under one or more agreements.
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using DataImport.Models;

namespace DataImport.Common.Helpers
{
public interface IAuthRequestWrapper
{
string GetAccessCode(ApiServer apiServer, string encryptionKey);

string GetToken(ApiServer apiServer, string encryptionKey, string accessCode);

string GetToken(ApiServer apiServer, string encryptionKey);
}
}
71 changes: 6 additions & 65 deletions DataImport.Common/Helpers/OAuthRequestWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,23 @@
// See the LICENSE and NOTICES files in the project root for more information.

using System;
using System.Net;
using System.Security.Authentication;
using DataImport.Models;
using RestSharp;
using static DataImport.Common.Encryption;

namespace DataImport.Common.Helpers
{
public interface IOAuthRequestWrapper
public class OAuthRequestWrapper : AuthRequestWrapper, IAuthRequestWrapper
{
string GetAccessCode(ApiServer apiServer, string encryptionKey);

string GetBearerToken(ApiServer apiServer, string encryptionKey, string accessCode);

string GetBearerToken(ApiServer apiServer, string encryptionKey);
}

public class OAuthRequestWrapper : IOAuthRequestWrapper
{
public string GetAccessCode(ApiServer apiServer, string encryptionKey)
public string GetToken(ApiServer apiServer, string encryptionKey)
{
var authUrl = new Uri(apiServer.AuthUrl);
var oauthClient = new RestClient(authUrl.GetLeftPart(UriPartial.Authority));

var accessCodeRequest = new RestRequest(authUrl.AbsolutePath, Method.Post);
var apiServerKey = !string.IsNullOrEmpty(encryptionKey)
? Decrypt(apiServer.Key, encryptionKey)
: apiServer.Key;
accessCodeRequest.AddParameter("Client_id", apiServerKey);
accessCodeRequest.AddParameter("Response_type", "code");

var accessCodeResponse = oauthClient.Execute<AccessCodeResponse>(accessCodeRequest);

if (accessCodeResponse.StatusCode != HttpStatusCode.OK)
throw new AuthenticationException("Unable to retrieve an authorization code. Error message: " +
accessCodeResponse.ErrorMessage);
if (accessCodeResponse.Data.Error != null)
throw new AuthenticationException(
"Unable to retrieve an authorization code. Please verify that your application key is correct. Alternately, the service address may not be correct: " +
authUrl);

return accessCodeResponse.Data.Code;
return GetToken(apiServer, encryptionKey, null);
}

public string GetBearerToken(ApiServer apiServer, string encryptionKey)
{
return GetBearerToken(apiServer, encryptionKey, null);
}

public string GetBearerToken(ApiServer apiServer, string encryptionKey, string accessCode)
public string GetToken(ApiServer apiServer, string encryptionKey, string accessCode)
{
var tokenUrl = new Uri(apiServer.TokenUrl);
RestClientOptions options;

if (ScriptExtensions.IgnoresCertificateErrors())
{
#pragma warning disable S4830
options = new RestClientOptions(tokenUrl.GetLeftPart(UriPartial.Authority))
{
RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
};
#pragma warning restore S4830
}
else
{
options = new RestClientOptions(tokenUrl.GetLeftPart(UriPartial.Authority));
}
RestClientOptions options = GetOptions(tokenUrl);

var oauthClient = new RestClient(options);

Expand All @@ -95,16 +45,7 @@ public string GetBearerToken(ApiServer apiServer, string encryptionKey, string a
bearerTokenRequest.AddParameter("grant_type", "client_credentials");
}

var bearerTokenResponse = oauthClient.Execute<BearerTokenResponse>(bearerTokenRequest);
if (bearerTokenResponse.StatusCode != HttpStatusCode.OK)
throw new AuthenticationException("Unable to retrieve an access token. Error message: " +
bearerTokenResponse.ErrorMessage);

if (bearerTokenResponse.Data.Error != null || bearerTokenResponse.Data.TokenType != "bearer")
throw new AuthenticationException(
"Unable to retrieve an access token. Please verify that your application secret is correct.");

return bearerTokenResponse.Data.AccessToken;
return GetToken(bearerTokenRequest, oauthClient);
}
}
}
8 changes: 4 additions & 4 deletions DataImport.Common/Helpers/OdsApiTokenRetriever.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class OdsApiTokenRetriever : ITokenRetriever
{
private readonly ApiServer _apiServer;
private readonly string _encryptionKey;
private readonly IOAuthRequestWrapper _requestWrapper;
private readonly IAuthRequestWrapper _requestWrapper;

public OdsApiTokenRetriever(IOAuthRequestWrapper requestWrapper, ApiServer apiServer, string encryptionKey = "")
public OdsApiTokenRetriever(IAuthRequestWrapper requestWrapper, ApiServer apiServer, string encryptionKey = "")
{
_requestWrapper = requestWrapper ?? throw new ArgumentNullException(nameof(requestWrapper));

Expand All @@ -30,10 +30,10 @@ public string ObtainNewBearerToken()
{
var accessCode = _requestWrapper.GetAccessCode(_apiServer, _encryptionKey);

return _requestWrapper.GetBearerToken(_apiServer, _encryptionKey, accessCode);
return _requestWrapper.GetToken(_apiServer, _encryptionKey, accessCode);
}

return _requestWrapper.GetBearerToken(_apiServer, _encryptionKey);
return _requestWrapper.GetToken(_apiServer, _encryptionKey);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ namespace DataImport.Common.Preprocessors
public class PowerShellPreprocessorService : IPowerShellPreprocessorService
{
private readonly PowerShellPreprocessorOptions _psPreprocessorOptions;
private readonly IOAuthRequestWrapper _authRequestWrapper;
private readonly IAuthRequestWrapper _authRequestWrapper;
private readonly IPowerShellPreprocessSettings _powerShellPreprocessSettings;

public PowerShellPreprocessorService(IPowerShellPreprocessSettings powerShellPreprocessSettings, PowerShellPreprocessorOptions options, IOAuthRequestWrapper authRequestWrapper)
public PowerShellPreprocessorService(IPowerShellPreprocessSettings powerShellPreprocessSettings, PowerShellPreprocessorOptions options, IAuthRequestWrapper authRequestWrapper)
{
_psPreprocessorOptions = options ?? throw new ArgumentNullException(nameof(options));
_authRequestWrapper = authRequestWrapper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public async Task ShouldCorrectlyGenerateFile()
var fileServices = GetRegisteredFileServices();
var dbContext = Services.GetService<DataImportDbContext>();
var options = Services.GetService<IOptions<AppSettings>>();
var service = new PowerShellPreprocessorService(options.Value, new PowerShellPreprocessorOptions(), A.Fake<IOAuthRequestWrapper>());
var service = new PowerShellPreprocessorService(options.Value, new PowerShellPreprocessorOptions(), A.Fake<IAuthRequestWrapper>());
var extService = Services.GetService<ExternalPreprocessorService>();
var commandHandler = new CommandHandlerTestWrapper(logger, options, dbContext, fileServices, service, extService);

Expand Down Expand Up @@ -79,7 +79,7 @@ public async Task ShouldRunAgentsInOrder()
var fileServices = GetRegisteredFileServices();
var dbContext = Services.GetService<DataImportDbContext>();
var options = Services.GetService<IOptions<AppSettings>>();
var service = new PowerShellPreprocessorService(options.Value, new PowerShellPreprocessorOptions(), A.Fake<IOAuthRequestWrapper>());
var service = new PowerShellPreprocessorService(options.Value, new PowerShellPreprocessorOptions(), A.Fake<IAuthRequestWrapper>());
var extService = Services.GetService<ExternalPreprocessorService>();
var commandHandler = new CommandHandlerTestWrapper(logger, options, dbContext, fileServices, service, extService);

Expand Down Expand Up @@ -147,7 +147,7 @@ public async Task ShouldRunOnlyEnabledAgents()
var fileServices = GetRegisteredFileServices();
var dbContext = Services.GetService<DataImportDbContext>();
var options = Services.GetService<IOptions<AppSettings>>();
var service = new PowerShellPreprocessorService(options.Value, new PowerShellPreprocessorOptions(), A.Fake<IOAuthRequestWrapper>());
var service = new PowerShellPreprocessorService(options.Value, new PowerShellPreprocessorOptions(), A.Fake<IAuthRequestWrapper>());
var extService = Services.GetService<ExternalPreprocessorService>();
var commandHandler = new CommandHandlerTestWrapper(logger, options, dbContext, fileServices, service, extService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ function ReformatDate($value, $from, $to) {
[SetUp]
public void Setup()
{
var oAuthRequestWrapper = A.Fake<IOAuthRequestWrapper>();
var oAuthRequestWrapper = A.Fake<IAuthRequestWrapper>();
A.CallTo(() => oAuthRequestWrapper.GetAccessCode(null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetBearerToken(null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetBearerToken(null, null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetToken(null, null)).WithAnyArguments().Returns("fake token");
A.CallTo(() => oAuthRequestWrapper.GetToken(null, null, null)).WithAnyArguments().Returns("fake token");

var appSettings = new AppSettings { EncryptionKey = Guid.NewGuid().ToString() };
_service = new PowerShellPreprocessorService(appSettings, new PowerShellPreprocessorOptions(), oAuthRequestWrapper);
Expand Down
3 changes: 2 additions & 1 deletion DataImport.Server.TransformLoad.Tests/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"FileMode": "Local",
"ShareName": "(Initialized During Execution)",
"UsePowerShellWithNoRestrictions": false,
"MinimumLevelIngestionLog": "INFORMATION"
"MinimumLevelIngestionLog": "INFORMATION",
"UseBasicAuthentication": false
},
"Concurrency": {
"LimitConcurrentApiPosts": true,
Expand Down
1 change: 1 addition & 0 deletions DataImport.Server.TransformLoad/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class AppSettings : IFileSettings, IPowerShellPreprocessSettings, IEncryp
public bool UsePowerShellWithNoRestrictions { get; set; }
public string MinimumLevelIngestionLog { get; set; }
public bool IgnoresCertificateErrors { get; set; } = false;
public bool UseBasicAuthentication { get; set; } = false;
}

public class ConcurrencySettings
Expand Down
6 changes: 5 additions & 1 deletion DataImport.Server.TransformLoad/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ public static IServiceCollection ConfigureTransformLoadServices(this IServiceCol
return resolver.Resolve();
});
services.AddTransient<IExternalPreprocessorService, ExternalPreprocessorService>();
services.AddTransient<IOAuthRequestWrapper, OAuthRequestWrapper>();

if (bool.Parse(configuration.GetSection("AppSettings")["UseBasicAuthentication"]))
services.AddTransient<IAuthRequestWrapper, BasicAuthRequestWrapper>();
else
services.AddTransient<IAuthRequestWrapper, OAuthRequestWrapper>();

services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));

Expand Down
Loading

0 comments on commit 9f8c5a1

Please sign in to comment.