diff --git a/.github/workflows/monthly-scan.yml b/.github/workflows/monthly-scan.yml new file mode 100644 index 0000000..bc5d7f5 --- /dev/null +++ b/.github/workflows/monthly-scan.yml @@ -0,0 +1,51 @@ +name: Trufflehog Monthly Deep Scan + + +on: + schedule: + - cron: "0 0 28-31 * *" + + #This scan will run on the last day of every month + + push: + branches: [main, p3sprint/*, feature/*] +jobs: + trufflehog-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Install Trufflehog-latest + run: | + pip3 install trufflehog + - name: Run Trufflehog Monthly Deep Scan + + #Configure the correct git url of the repo to be scanned + run: | + trufflehog https://github.com/Crown-Commercial-Service/ccs-ppg-notification-api.git --regex --entropy=FALSE + + - name: Send email notification + uses: + dawidd6/action-send-mail@v3.1.0 + if: always() + + with: + server_address: ${{ secrets.SERVER_ADDRESS }} + server_port: ${{ secrets.SERVER_PORT }} + username: ${{ secrets.USER_NAME }} + password: ${{ secrets.PASSWORD }} + subject: Trufflehog Monthly Deep Scan + to: "rahulgandhi.jayabalan@brickendon.com,ponselvam.sakthivel@brickendon.com" + from: "secops@brickendon.com" + body: | + + Hi, + + The Trufflehog Monthly Deep Scan has completed for "${{ github.repository }}". Please review the results below: + + Scan Job URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + Scan Status: **${{ job.status }}** + + Thank You. + Brickendon SecOps diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml new file mode 100644 index 0000000..5c41624 --- /dev/null +++ b/.github/workflows/secrets-scan.yml @@ -0,0 +1,64 @@ +name: Git Secrets Scan + +on: + schedule: + - cron: "0 0 * * *" + push: + branches: [main, feature/*, p3sprint/*] + +jobs: + git-secrets-scan: + name: Git Secrets Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install git secrets + run: sudo apt-get update && sudo apt-get install git-secrets -y + + - name: Add custom secrets patterns + run: git secrets --add '(\bBEGIN\b).*(PRIVATE KEY\b)' + && git secrets --add 'AKIA[0-9A-Z]{16}' + && git secrets --add '^([A-Za-z0-9/+=]{40,})$' + && git secrets --add '^ghp_[a-zA-Z0-9]{36}' + && git secrets --add '^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}' + && git secrets --add '^v[0-9]\\.[0-9a-f]{40}' + && git secrets --add '[A-Za-z0-9+/]{88}==' + && git secrets --add '[A-Za-z0-9_-]{32}$' + && git secrets --add 'conclavesso[0-9a-z-]{84}' + && git secrets --add '\\b[a-z0-9]{80}\\b' + && git secrets --add '\\b[A-Z0-9]{50}\\b' + && git secrets --add '\\b[A-Z0-9]{58}\\b' + && git secrets --add '^[a-zA-Z0-9_-]{32,64}$' + + - name: Run git secrets scan + run: | + git secrets --scan + + - name: Send email notification + uses: + dawidd6/action-send-mail@v3.1.0 + if: always() + + with: + server_address: ${{ secrets.SERVER_ADDRESS }} + server_port: ${{ secrets.SERVER_PORT }} + username: ${{ secrets.USER_NAME }} + password: ${{ secrets.PASSWORD }} + subject: Git Secrets Scan Results + to: "ponselvam.sakthivel@brickendon.com, rahulgandhi.jayabalan@brickendon.com" + from: "secops@brickendon.com" + body: | + + Hi, + + The Git Secrets scan has completed for "${{ github.repository }}". Please review the results below: + + Scan Job URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + Scan Status: **${{ job.status }}** + + Thank You. + Brickendon SecOps diff --git a/Ccs.Ppg.NotificationService.API/Ccs.Ppg.NotificationService.API.csproj b/Ccs.Ppg.NotificationService.API/Ccs.Ppg.NotificationService.API.csproj index 4c2a553..0ff6047 100644 --- a/Ccs.Ppg.NotificationService.API/Ccs.Ppg.NotificationService.API.csproj +++ b/Ccs.Ppg.NotificationService.API/Ccs.Ppg.NotificationService.API.csproj @@ -12,20 +12,26 @@ - + - + - + + + + + + + diff --git a/Ccs.Ppg.NotificationService.API/Controllers/NotificationController.cs b/Ccs.Ppg.NotificationService.API/Controllers/NotificationController.cs index 33eebda..65247df 100644 --- a/Ccs.Ppg.NotificationService.API/Controllers/NotificationController.cs +++ b/Ccs.Ppg.NotificationService.API/Controllers/NotificationController.cs @@ -12,10 +12,12 @@ namespace Ccs.Ppg.NotificationService.API.Controllers public class NotificationController : ControllerBase { private readonly IMessageProviderService _messageProviderService; - public NotificationController(IMessageProviderService messageProviderService) + private readonly IEmailProviderService _emailProviderService; + public NotificationController(IMessageProviderService messageProviderService, IEmailProviderService emailProviderService) { _messageProviderService = messageProviderService; - } + _emailProviderService = emailProviderService; + } /// /// Allows a user to send SMS /// @@ -39,11 +41,42 @@ public NotificationController(IMessageProviderService messageProviderService) /// [HttpPost("sms")] - [SwaggerOperation(Tags = new[] { "notification/sms" })] + [SwaggerOperation(Tags = new[] { "Notification - SMS" })] [ProducesResponseType(typeof(bool), 200)] public async Task Post(MessageRequestModel message) { return await _messageProviderService.SendMessage(message); - } - } + } + + /// + /// Allows a user to send email + /// + /// Ok + /// Unauthorised + /// Forbidden + /// Not found + /// Bad request. + /// + /// Sample request: + /// + /// POST /notification/email + /// { + /// "to": "username@xxxx.com", + /// "templateId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + /// "bodyContent": { + /// "email": "UserName@xxxx.com", + /// "additionalProp3": "string", + /// "additionalProp3": "string" + /// } + /// } + /// + /// + + [HttpPost("email")] + [SwaggerOperation(Tags = new[] { "Notification - Email" })] + public async Task SendEmailAsync(EmailInfo emailInfo) + { + await _emailProviderService.SendEmailAsync(emailInfo); + } + } } diff --git a/Ccs.Ppg.NotificationService.API/CustomOptions/ParameterStoreConfigurationProvider.cs b/Ccs.Ppg.NotificationService.API/CustomOptions/ParameterStoreConfigurationProvider.cs index be45477..fed5754 100644 --- a/Ccs.Ppg.NotificationService.API/CustomOptions/ParameterStoreConfigurationProvider.cs +++ b/Ccs.Ppg.NotificationService.API/CustomOptions/ParameterStoreConfigurationProvider.cs @@ -39,7 +39,9 @@ public async Task GetSecrets() configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "Message/ApiKey", "Message:ApiKey")); configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "Message/TemplateId", "Message:TemplateId")); - var dbName = _awsParameterStoreService.FindParameterByName(parameters, path + "ConnectionStrings/Name"); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "Email/ApiKey", "Email:ApiKey")); + + var dbName = _awsParameterStoreService.FindParameterByName(parameters, path + "ConnectionStrings/Name"); var dbConnection = _awsParameterStoreService.FindParameterByName(parameters, path + "ConnectionStrings/CcsSso"); if (!string.IsNullOrEmpty(dbName)) @@ -53,6 +55,22 @@ public async Task GetSecrets() } configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "apis/OrganisationUrl", "apis:OrganisationUrl")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "EnableXRay", "EnableXRay")); + + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "WrapperApiSettings/ConfigApiKey", "WrapperApiSettings:ConfigApiKey")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "WrapperApiSettings/ApiGatewayEnabledConfigUrl", "WrapperApiSettings:ApiGatewayEnabledConfigUrl")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "WrapperApiSettings/ApiGatewayDisabledConfigUrl", "WrapperApiSettings:ApiGatewayDisabledConfigUrl")); + + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/EnableValidation", "NotificationValidationConfigurations:EnableValidation")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/SmsMsgLength", "NotificationValidationConfigurations:SmsMsgLength")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/OrgNameLegnth", "NotificationValidationConfigurations:OrgNameLegnth")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/EmailRegex", "NotificationValidationConfigurations:EmailRegex")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/FirstNameLength", "NotificationValidationConfigurations:FirstNameLength")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/LastNameLength", "NotificationValidationConfigurations:LastNameLength")); + configurations.AddRange(_awsParameterStoreService.GetParameterFromCommaSeparated(parameters, path + "NotificationValidationConfigurations/SignInProviders", "NotificationValidationConfigurations:SignInProviders")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/LinkRegex", "NotificationValidationConfigurations:LinkRegex")); + configurations.Add(_awsParameterStoreService.GetParameter(parameters, path + "NotificationValidationConfigurations/CcsMsg", "NotificationValidationConfigurations:CcsMsg")); + foreach (var configuration in configurations) { diff --git a/Ccs.Ppg.NotificationService.API/CustomOptions/VaultConfiguration.cs b/Ccs.Ppg.NotificationService.API/CustomOptions/VaultConfiguration.cs index 3be7052..ab881d5 100644 --- a/Ccs.Ppg.NotificationService.API/CustomOptions/VaultConfiguration.cs +++ b/Ccs.Ppg.NotificationService.API/CustomOptions/VaultConfiguration.cs @@ -56,7 +56,13 @@ public async Task GetSecrets() Data.Add("Message:TemplateId", messageSettingsVault.TemplateId); } - if (_secrets.Data.ContainsKey("ConnectionStrings")) + if (_secrets.Data.ContainsKey("Email")) + { + var messageSettingsVault = JsonConvert.DeserializeObject(_secrets.Data["Email"].ToString()); + Data.Add("Email:ApiKey", messageSettingsVault.ApiKey); + } + + if (_secrets.Data.ContainsKey("ConnectionStrings")) { var connectionStringsSettingsVault = JsonConvert.DeserializeObject(_secrets.Data["ConnectionStrings"].ToString()); Data.Add("ConnectionStrings:CcsSso", connectionStringsSettingsVault.CcsSso); diff --git a/Ccs.Ppg.NotificationService.API/Program.cs b/Ccs.Ppg.NotificationService.API/Program.cs index 8645124..fa79eaf 100644 --- a/Ccs.Ppg.NotificationService.API/Program.cs +++ b/Ccs.Ppg.NotificationService.API/Program.cs @@ -1,3 +1,5 @@ +using Amazon.XRay.Recorder.Core; +using Amazon.XRay.Recorder.Handlers.AwsSdk; using Ccs.Ppg.NotificationService.API; using Ccs.Ppg.NotificationService.API.CustomOptions; @@ -30,9 +32,22 @@ // Add services to the container. builder.Services.ConfigureServices(builder.Configuration); +string startupUrl = Environment.GetEnvironmentVariable("STARTUP_URL"); +if (!string.IsNullOrWhiteSpace(startupUrl)) +{ + builder.WebHost.UseUrls(startupUrl); +} + var app = builder.Build(); -app.ConfigurePipeline(); +if (!string.IsNullOrEmpty(builder.Configuration["EnableXRay"]) && Convert.ToBoolean(builder.Configuration["EnableXRay"])) +{ + Console.WriteLine("x-ray is enabled."); + AWSXRayRecorder.InitializeInstance(configuration: builder.Configuration); + app.UseXRay("NotificationApi"); + AWSSDKHandler.RegisterXRayForAllServices(); +} +app.ConfigurePipeline(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/Ccs.Ppg.NotificationService.API/Properties/launchSettings.json b/Ccs.Ppg.NotificationService.API/Properties/launchSettings.json index 9a5270e..df9d9c6 100644 --- a/Ccs.Ppg.NotificationService.API/Properties/launchSettings.json +++ b/Ccs.Ppg.NotificationService.API/Properties/launchSettings.json @@ -13,7 +13,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "notification-service/swagger", "applicationUrl": "https://localhost:7247;http://localhost:5247", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Ccs.Ppg.NotificationService.API/Startup.cs b/Ccs.Ppg.NotificationService.API/Startup.cs index baf8d21..7c0bcb9 100644 --- a/Ccs.Ppg.NotificationService.API/Startup.cs +++ b/Ccs.Ppg.NotificationService.API/Startup.cs @@ -1,5 +1,7 @@ -using Ccs.Ppg.Utility.Authorization; +using Ccs.Ppg.NotificationService.Model; +using Ccs.Ppg.Utility.Authorization; using Ccs.Ppg.Utility.Cache; +using Ccs.Ppg.Utility.Exceptions; using Ccs.Ppg.Utility.Logging; using Ccs.Ppg.Utility.Swagger; using System.Reflection; @@ -7,7 +9,7 @@ namespace Ccs.Ppg.NotificationService.API { public static class Startup - { + { public static void ConfigureServices(this IServiceCollection services, ConfigurationManager config) { services.AddCustomLogging(); @@ -18,6 +20,60 @@ public static void ConfigureServices(this IServiceCollection services, Configura services.AddServices(config); services.AddControllers(); services.AddRedis(config); + services.AddSingleton(s => + { + bool.TryParse(config["IsApiGatewayEnabled"], out bool isApiGatewayEnabled); + bool.TryParse(config["EnableXRay"], out bool enableXray); + bool.TryParse(config["RedisCache:IsEnabled"], out bool redisCacheEnabled); + bool.TryParse(config["NotificationValidationConfigurations:EnableValidation"], out bool validationEnabled); + int.TryParse(config["NotificationValidationConfigurations:SmsMsgLength"], out int smsMsgLength); + int.TryParse(config["NotificationValidationConfigurations:OrgNameLegnth"], out int orgNameLegnth); + int.TryParse(config["NotificationValidationConfigurations:FirstNameLength"], out int firstNameLength); + int.TryParse(config["NotificationValidationConfigurations:LastNameLength"], out int LastNameLength); + ApplicationConfigurationInfo appConfigInfo = new ApplicationConfigurationInfo() + { + IsApiGatewayEnabled = isApiGatewayEnabled, + ApiKey = config["ApiKey"], + OrganisationApiUrl = config["OrganisationApiUrl"], + EnableXRay = enableXray, + RedisCacheSettings = new RedisCacheSettings() + { + IsEnabled = redisCacheEnabled + }, + MessageSettings = new MessageSettings() + { + ApiKey = config["Message:ApiKey"], + TemplateId = config["Message:TemplateId"] + }, + ConnectionStrings = new ConnectionStrings() + { + CcsSso = config["ConnectionStrings:CcsSso"] + }, + EmailSettings = new EmailSettings() + { + ApiKey = config["Email:ApiKey"] + }, + WrapperApiSettings = new WrapperApiSettings() + { + ApiGatewayDisabledConfigUrl = config["WrapperApiSettings:ApiGatewayDisabledConfigUrl"], + ApiGatewayEnabledConfigUrl = config["WrapperApiSettings:ApiGatewayEnabledConfigUrl"], + ConfigApiKey = config["WrapperApiSettings:ConfigApiKey"] + }, + NotificationValidationConfigurations = new NotificationValidationConfigurations() + { + EnableValidation = validationEnabled, + SmsMsgLength = smsMsgLength, + OrgNameLegnth = orgNameLegnth, + FirstNameLength = firstNameLength, + LastNameLength = LastNameLength, + EmailRegex = config["NotificationValidationConfigurations:EmailRegex"], + LinkRegex = config["NotificationValidationConfigurations:LinkRegex"], + CcsMsg = config["NotificationValidationConfigurations:CcsMsg"], + SignInProviders = config.GetSection("NotificationValidationConfigurations:SignInProviders").Get>(), + } + }; + return appConfigInfo; + }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle services.AddEndpointsApiExplorer(); @@ -29,10 +85,9 @@ public static void ConfigureServices(this IServiceCollection services, Configura public static void ConfigurePipeline(this WebApplication app) { - if (app.Environment.IsDevelopment()) - { - app.ConfigureSwagger(); - } + app.ConfigureSwagger(); + + app.UseMiddleware(); app.UseHttpsRedirection(); diff --git a/Ccs.Ppg.NotificationService.API/Swagger/notification-service-swagger.json b/Ccs.Ppg.NotificationService.API/Swagger/notification-service-swagger.json new file mode 100644 index 0000000..34f7455 --- /dev/null +++ b/Ccs.Ppg.NotificationService.API/Swagger/notification-service-swagger.json @@ -0,0 +1,178 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Ccs.Ppg.NotificationService.API", + "version": "v1" + }, + "paths": { + "/notification-service/sms": { + "post": { + "tags": [ + "Notification Service - SMS" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MessageRequestModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/MessageRequestModel" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/MessageRequestModel" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "boolean" + } + }, + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + } + } + } + } + } + }, + "/notification-service/email": { + "post": { + "tags": [ + "Notification Service - Email" + ], + "summary": "Allows a user to send email", + "description": "Sample request:\r\n \r\n POST /notification-service/email\r\n {\r\n \"to\": \"username@xxxx.com\",\r\n \"templateId\": \"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\",\r\n \"bodyContent\": {\r\n \"email\": \"UserName@xxxx.com\",\r\n \"additionalProp3\": \"string\",\r\n \"additionalProp3\": \"string\"\r\n }\r\n }", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailInfo" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/EmailInfo" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/EmailInfo" + } + } + } + }, + "responses": { + "200": { + "description": "Ok" + }, + "400": { + "description": "Bad request." + }, + "401": { + "description": "Unauthorised" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + } + } + } + }, + "components": { + "schemas": { + "EmailInfo": { + "required": [ + "templateId", + "to" + ], + "type": "object", + "properties": { + "to": { + "type": "string" + }, + "templateId": { + "type": "string" + }, + "bodyContent": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "MessageInfo": { + "type": "object", + "properties": { + "key": { + "type": "string", + "nullable": true + }, + "message": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "MessageRequestModel": { + "required": [ + "message", + "phoneNumber", + "templateId" + ], + "type": "object", + "properties": { + "phoneNumber": { + "type": "string" + }, + "templateId": { + "type": "string" + }, + "message": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageInfo" + } + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "name": "X-API-KEY", + "in": "header" + } + } + }, + "security": [ + { + "ApiKey": [] + } + ] +} \ No newline at end of file diff --git a/Ccs.Ppg.NotificationService.API/appsecrets.json b/Ccs.Ppg.NotificationService.API/appsecrets.json index 08a6438..e31b52b 100644 --- a/Ccs.Ppg.NotificationService.API/appsecrets.json +++ b/Ccs.Ppg.NotificationService.API/appsecrets.json @@ -14,6 +14,26 @@ }, "apis": { "OrganisationUrl": "" + }, + "Email": { + "ApiKey": "" + }, + "EnableXRay": "false", + "WrapperApiSettings": { + "ConfigApiKey": "", + "ApiGatewayEnabledConfigUrl": "", + "ApiGatewayDisabledConfigUrl": "" + }, + "NotificationValidationConfigurations": { + "EnableValidation": true, + "SmsMsgLength": 6, + "OrgNameLegnth": 150, + "EmailRegex": "", + "FirstNameLength": 50, + "LastNameLength": 50, + "SignInProviders": [], + "LinkRegex": "", + "CcsMsg": "" } } diff --git a/Ccs.Ppg.NotificationService.Tests/Ccs.Ppg.NotificationService.Tests.csproj b/Ccs.Ppg.NotificationService.Tests/Ccs.Ppg.NotificationService.Tests.csproj new file mode 100644 index 0000000..68481a1 --- /dev/null +++ b/Ccs.Ppg.NotificationService.Tests/Ccs.Ppg.NotificationService.Tests.csproj @@ -0,0 +1,31 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/Ccs.Ppg.NotificationService.Tests/Infrastructure/DtoHelper.cs b/Ccs.Ppg.NotificationService.Tests/Infrastructure/DtoHelper.cs new file mode 100644 index 0000000..418cfe5 --- /dev/null +++ b/Ccs.Ppg.NotificationService.Tests/Infrastructure/DtoHelper.cs @@ -0,0 +1,31 @@ +using Ccs.Ppg.NotificationService.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ccs.Ppg.NotificationService.Tests.Infrastructure +{ + public class DtoHelper + { + public static EmailInfo GetEmailInfoRequest(string toMailId,string templateId, Dictionary BodyContent) + { + return new EmailInfo + { + To = toMailId, + TemplateId = templateId, + BodyContent = BodyContent + }; + } + public static MessageRequestModel GetSMSInfoRequest(string mobileNumber, string templateId, List BodyContent) + { + return new MessageRequestModel + { + PhoneNumber = mobileNumber, + TemplateId = templateId, + Message = BodyContent + }; + } + } +} diff --git a/Ccs.Ppg.NotificationService.Tests/Services/EmailProviderServiceTests.cs b/Ccs.Ppg.NotificationService.Tests/Services/EmailProviderServiceTests.cs new file mode 100644 index 0000000..1306814 --- /dev/null +++ b/Ccs.Ppg.NotificationService.Tests/Services/EmailProviderServiceTests.cs @@ -0,0 +1,342 @@ +using Amazon.Runtime; +using Ccs.Ppg.NotificationService.Model; +using Ccs.Ppg.NotificationService.Services; +using Ccs.Ppg.NotificationService.Services.IServices; +using Ccs.Ppg.NotificationService.Tests.Infrastructure; +using Ccs.Ppg.Utility.Constants.Constants; +using Ccs.Ppg.Utility.Exceptions.Exceptions; +using Moq; +using Notify.Client; +using Notify.Interfaces; +using Notify.Models.Responses; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static System.Net.WebRequestMethods; + +namespace Ccs.Ppg.NotificationService.Tests.Services +{ + public class EmailProviderServiceTests + { + private readonly Mock _mockHttpClientFactory; + private readonly Mock _mockWrapperConfigurationService; + private readonly ApplicationConfigurationInfo _mockApplicationConfigurationInfo; + + public EmailProviderServiceTests() + { + _mockHttpClientFactory = new Mock(); + _mockWrapperConfigurationService = new Mock(); + _mockApplicationConfigurationInfo = new ApplicationConfigurationInfo(); + } + + public static List SetupConfigRoles() + { + List configRoles = new List(); + configRoles.AddRange(new List { "sampleRole1", "sampleRole2", "sampleRole3" }); + return configRoles; + } + + public class SendEmail + { + public static IEnumerable InvalidEmailTestData => new List + { + new object[] + { + // InvalidToEmail + DtoHelper.GetEmailInfoRequest("newttestoorg", Guid.NewGuid().ToString(), new Dictionary + { + { "link", "https://sample.test.check.com" }, + { "emailid", "changeuserpwd@yopmail.com" } + }), + }, + new object[] + { + // InvalidTemplateId + DtoHelper.GetEmailInfoRequest("abc@yopmail.com", "12345", new Dictionary + { + { "link", "https://sample.test.check.com" }, + { "emailid", "changeuserpwd@yopmail.com" } + }) + }, + new object[] + { + // InvalidEmailAddress + DtoHelper.GetEmailInfoRequest("newttestoorg", Guid.NewGuid().ToString(), new Dictionary + { + { "link", "https://sample.test.check.com" }, + { "emailid", "changeuserpwd" } + }), + }, + new object[] + { + // InvalidCcsMessage + DtoHelper.GetEmailInfoRequest("newttestoorg@yopmail.com", Guid.NewGuid().ToString(), new Dictionary + { + { "link", "https://sample.test.check.com" }, + { "CCSMsg", "InvalidMessage" } + }) + }, + new object[] + { + // InvalidServiceNames + DtoHelper.GetEmailInfoRequest("newttestoorg@yopmail.com", Guid.NewGuid().ToString(), new Dictionary + { + { "orgName", "TestingOrg" }, + { "serviceNames", "Invalid_Service" }, + { "link", "https://sample.test.check.com" } + }) + }, + new object[] + { + // InvalidName + DtoHelper.GetEmailInfoRequest("newttestoorg@yopmail.com", Guid.NewGuid().ToString(), new Dictionary + { + { "firstname", "INVALID_NAME_EXCEEDS_MAX_LENGTH" }, + { "lastname", "TestUser" }, + { "email", "newtestuser@yopmail.com" } + }) + }, + new object[] + { + // InvalidLink + DtoHelper.GetEmailInfoRequest("newttestoorg@yopmail.com", Guid.NewGuid().ToString(), new Dictionary + { + { "OrgRegistersationlink", "INVALID_LINK" }, + { "emailaddress", "newtestuser@yopmail.com" } + }) + }, + new object[] + { + // InvalidOrgName + DtoHelper.GetEmailInfoRequest("newttestoorg@yopmail.com", Guid.NewGuid().ToString(), new Dictionary + { + { "orgName", "" }, + { "link", "https://sample.test.check.com" } + }) + }, + new object[] + { + // InvalidSigninProviders + DtoHelper.GetEmailInfoRequest("newttestoorg@yopmail.com", Guid.NewGuid().ToString(), new Dictionary + { + { "sigininproviders", "Invalid_Providers" }, + { "link", "https://sample.test.check.com" } + }) + } + }; + + [Theory] + [MemberData(nameof(InvalidEmailTestData))] + public async Task InvalidDataException_WhenSendEmailAsync(EmailInfo mailRequest) + { + var mockWrapperConfigurationService = new Mock(); + mockWrapperConfigurationService.Setup(s => s.GetServices()).ReturnsAsync(SetupConfigRoles()); + ApplicationConfigurationInfo applicationConfigurationInfo = new ApplicationConfigurationInfo() + { + NotificationValidationConfigurations = new NotificationValidationConfigurations() + { + EnableValidation = true, + OrgNameLegnth = 25, + EmailRegex = "^\\s?([\\w!#$%+&'*-/=?^_`{|}~][^,]*)@([\\w\\.\\-]+)((\\.(\\w){1,1000})+)\\s?$", + FirstNameLength = 10, + LastNameLength = 10, + SignInProviders = new List { "Twitter,Google,Apple,Amazon" }, + LinkRegex = "^https?:\\/\\/([-a-zA-Z0-9@._]{1,25}\\.test\\.check\\.com)(?:\\/.*)?$\r\n", + CcsMsg = "Check before proceeding with the email link" + }, + EmailSettings = new EmailSettings() + { + ApiKey = "TestKey" + } + }; + var bodyContent = new Dictionary(); + mailRequest.BodyContent.ToList().ForEach(pair => bodyContent.Add(pair.Key, pair.Value)); + var httpClientFactoryMock = new Mock(); + var httpClientMock = new Mock(); + var httpClientWrapperMock = new Mock(httpClientMock.Object); + httpClientFactoryMock.Setup(f => f.CreateClient(It.IsAny())).Returns(httpClientMock.Object); + var emailProviderService = new EmailProviderService(httpClientFactoryMock.Object, mockWrapperConfigurationService.Object, applicationConfigurationInfo); + var ex = await Assert.ThrowsAsync(() => emailProviderService.SendEmailAsync(mailRequest)); + Assert.Equal(ErrorConstant.ErrorInvalidDetails, ex.Message); + } + + public class ValidateEmailMessage + { + public static IEnumerable InvalidEmailContent => new List + { + new object[] + { + new Dictionary + { + { "link", "https://dev.invalid.domain/reset" } + } + }, + new object[] + { + new Dictionary + { + { "orgname", "INVALID_ORGNAME_LENGTH" } + } + }, + new object[] + { + new Dictionary + { + { "emailid", "testuser" } + } + }, + new object[] + { + new Dictionary + { + { "firstname", "testuserinvalidlength" } + } + }, + new object[] + { + new Dictionary + { + { "lastname", "testuserinvalidlength" } + } + }, + new object[] + { + new Dictionary + { + { "servicename", "INVALID_SERVICE_NAME" } + } + }, + new object[] + { + new Dictionary + { + { "sigininproviders", "NOVA" } + } + }, + new object[] + { + new Dictionary + { + { "ccsmsg", "INVALID_CCS_MESSAGE" } + } + } + }; + + [Theory] + [MemberData(nameof(InvalidEmailContent))] + public async Task ReturnsFalse_WhenInvalidEmailContent(Dictionary emailContent) + { + var mockWrapperConfigurationService = new Mock(); + mockWrapperConfigurationService.Setup(s => s.GetServices()).ReturnsAsync(SetupConfigRoles()); + ApplicationConfigurationInfo applicationConfigurationInfo = new ApplicationConfigurationInfo() + { + NotificationValidationConfigurations = new NotificationValidationConfigurations() + { + EnableValidation = true, + OrgNameLegnth = 10, + EmailRegex = "^\\s?([\\w!#$%+&'*-/=?^_`{|}~][^,]*)@([\\w\\.\\-]+)((\\.(\\w){1,1000})+)\\s?$", + FirstNameLength = 10, + LastNameLength = 10, + SignInProviders = new List { "Twitter,Google,Apple,Amazon" }, + LinkRegex = "^https?:\\/\\/([-a-zA-Z0-9@._]{1,25}\\.test\\.check\\.com)(?:\\/.*)?$\r\n", + CcsMsg = "Check before proceeding with the email link" + } + }; + var emailProviderService = new EmailProviderService(null, mockWrapperConfigurationService.Object, applicationConfigurationInfo); + Assert.False(await emailProviderService.ValidateEmailMessage(emailContent)); + } + + public static IEnumerable ValidEmailContent => new List + { + new object[] + { + new Dictionary + { + { "link", "https://sample.test.check.com/" } + } + }, + new object[] + { + new Dictionary + { + { "orgname", "TestOrg" } + } + }, + new object[] + { + new Dictionary + { + { "emailid", "testuser@yopmail.com" } + } + }, + new object[] + { + new Dictionary + { + { "firstname", "testuser" } + } + }, + new object[] + { + new Dictionary + { + { "lastname", "check" } + } + }, + new object[] + { + new Dictionary + { + { "servicename", "sampleRole1,sampleRole2,sampleRole3" } + } + }, + new object[] + { + new Dictionary + { + { "sigininproviders", "Google" } + } + }, + new object[] + { + new Dictionary + { + { "ccsmsg", "Check before proceeding with the email link" } + } + } + }; + + [Theory] + [MemberData(nameof(ValidEmailContent))] + public async Task ReturnsTrue_WhenValidEmailContent(Dictionary emailContent) + { + var mockWrapperConfigurationService = new Mock(); + mockWrapperConfigurationService.Setup(s => s.GetServices()).ReturnsAsync(SetupConfigRoles()); + ApplicationConfigurationInfo applicationConfigurationInfo = new ApplicationConfigurationInfo() + { + NotificationValidationConfigurations = new NotificationValidationConfigurations() + { + EnableValidation = true, + OrgNameLegnth = 10, + EmailRegex = "^\\s?([\\w!#$%+&'*-/=?^_`{|}~][^,]*)@([\\w\\.\\-]+)((\\.(\\w){1,1000})+)\\s?$", + FirstNameLength = 10, + LastNameLength = 10, + SignInProviders = new List { "Twitter", "Google", "Apple", "Amazon" }, + LinkRegex = @"^https?:\/\/([-a-zA-Z0-9@._]{1,50}\.test\.check\.com)(?:\/.*)?$", + CcsMsg = "Check before proceeding with the email link" + } + }; + var emailProviderService = new EmailProviderService(null, mockWrapperConfigurationService.Object, applicationConfigurationInfo); + Assert.True(await emailProviderService.ValidateEmailMessage(emailContent)); + } + } + } + } + + +} diff --git a/Ccs.Ppg.NotificationService.Tests/Services/MessageProviderServiceTests.cs b/Ccs.Ppg.NotificationService.Tests/Services/MessageProviderServiceTests.cs new file mode 100644 index 0000000..08fef84 --- /dev/null +++ b/Ccs.Ppg.NotificationService.Tests/Services/MessageProviderServiceTests.cs @@ -0,0 +1,141 @@ +using Ccs.Ppg.NotificationService.Model; +using Ccs.Ppg.NotificationService.Services; +using Ccs.Ppg.NotificationService.Tests.Infrastructure; +using Moq; +using Notify.Client; +using Xunit; + +namespace Ccs.Ppg.NotificationService.Tests.Services +{ + public class MessageProviderServiceTests + { + private readonly Mock _mockHttpClientFactory; + private readonly ApplicationConfigurationInfo _mockApplicationConfigurationInfo; + + public MessageProviderServiceTests(IHttpClientFactory httpClientFactory, ApplicationConfigurationInfo applicationConfigurationInfo) + { + _mockHttpClientFactory = new Mock(); + _mockApplicationConfigurationInfo = new ApplicationConfigurationInfo(); + } + + public class SendMessage + { + MessageInfo msgInfo1 = new MessageInfo + { + key = "key", + Message = "value", + }; + + public static IEnumerable InvalidSMSData => new List + { + new object[] + { + DtoHelper.GetSMSInfoRequest("1234567890", "templateId", new List + { + new MessageInfo + { + key = "sms", + Message = "INVALID_SMS_MESSAGE_LENGTH", + } + }) + } + }; + + [Theory] + [MemberData(nameof(InvalidSMSData))] + public async Task InvalidDetailsException_WhenSendSMS(MessageRequestModel messageRequestInfo) + { + ApplicationConfigurationInfo applicationConfigurationInfo = new ApplicationConfigurationInfo() + { + NotificationValidationConfigurations = new NotificationValidationConfigurations() + { + EnableValidation = true, + SmsMsgLength = 10 + }, + EmailSettings = new EmailSettings() + { + ApiKey = "TestKey" + } + }; + + var httpClientFactoryMock = new Mock(); + var httpClientMock = new Mock(); + var httpClientWrapperMock = new Mock(httpClientMock.Object); + httpClientFactoryMock.Setup(f => f.CreateClient(It.IsAny())).Returns(httpClientMock.Object); + + var smsService = new MessageProviderService(httpClientFactoryMock.Object, applicationConfigurationInfo); + Assert.False(await smsService.SendMessage(messageRequestInfo)); + } + } + + public class ValidateMessage + { + public static IEnumerable InvalidSMSContent => new List + { + new object[] + { + new MessageInfo + { + key = "sms", + Message = "INVALID_SMS_MESSAGE_LENGTH", + } + }, + new object[] + { + new MessageInfo + { + key = "sms", + Message = "INVALID_SMS", + } + } + }; + + [Theory] + [MemberData(nameof(InvalidSMSContent))] + public void ReturnsFalse_WhenInvalidSMSContent(MessageInfo messageContent) + { + ApplicationConfigurationInfo applicationConfigurationInfo = new ApplicationConfigurationInfo() + { + NotificationValidationConfigurations = new NotificationValidationConfigurations() + { + EnableValidation = true, + SmsMsgLength = 10 + } + }; + + var smsProviderService = new MessageProviderService(null, applicationConfigurationInfo); + Assert.False(smsProviderService.ValidateMessage(messageContent)); + } + + public static IEnumerable ValidSMSContent => new List + { + new object[] + { + new MessageInfo + { + key = "sms", + Message = "VALID_SMS", + } + } + }; + + [Theory] + [MemberData(nameof(ValidSMSContent))] + public void ReturnsTrue_WhenValidSMSContent(MessageInfo messageContent) + { + ApplicationConfigurationInfo applicationConfigurationInfo = new ApplicationConfigurationInfo() + { + NotificationValidationConfigurations = new NotificationValidationConfigurations() + { + EnableValidation = true, + SmsMsgLength = 10 + } + }; + + var smsProviderService = new MessageProviderService(null, applicationConfigurationInfo); + Assert.True(smsProviderService.ValidateMessage(messageContent)); + } + } + } + +} diff --git a/Ccs.Ppg.NotificationService.sln b/Ccs.Ppg.NotificationService.sln index 35f9ebe..3f74c78 100644 --- a/Ccs.Ppg.NotificationService.sln +++ b/Ccs.Ppg.NotificationService.sln @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ccs.Ppg.Utility.Logging", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ccs.Ppg.Utility.Swagger", "Ccs.Ppg.Utility.Swagger\Ccs.Ppg.Utility.Swagger.csproj", "{02234335-EF98-48CF-A49E-13C8AF865AC3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ccs.Ppg.NotificationService.Tests", "Ccs.Ppg.NotificationService.Tests\Ccs.Ppg.NotificationService.Tests.csproj", "{51536928-1C8B-4179-ADFC-C449B3248370}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,10 @@ Global {02234335-EF98-48CF-A49E-13C8AF865AC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {02234335-EF98-48CF-A49E-13C8AF865AC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {02234335-EF98-48CF-A49E-13C8AF865AC3}.Release|Any CPU.Build.0 = Release|Any CPU + {51536928-1C8B-4179-ADFC-C449B3248370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51536928-1C8B-4179-ADFC-C449B3248370}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51536928-1C8B-4179-ADFC-C449B3248370}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51536928-1C8B-4179-ADFC-C449B3248370}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ccs.Ppg.NotificationService/Ccs.Ppg.NotificationService.csproj b/Ccs.Ppg.NotificationService/Ccs.Ppg.NotificationService.csproj index 7d9837f..f23374e 100644 --- a/Ccs.Ppg.NotificationService/Ccs.Ppg.NotificationService.csproj +++ b/Ccs.Ppg.NotificationService/Ccs.Ppg.NotificationService.csproj @@ -8,10 +8,18 @@ - + + + + + + + + + diff --git a/Ccs.Ppg.NotificationService/Model/ApplicationConfigurationInfo.cs b/Ccs.Ppg.NotificationService/Model/ApplicationConfigurationInfo.cs new file mode 100644 index 0000000..a9bc96d --- /dev/null +++ b/Ccs.Ppg.NotificationService/Model/ApplicationConfigurationInfo.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ccs.Ppg.NotificationService.Model +{ + public class ApplicationConfigurationInfo + { + public bool IsApiGatewayEnabled { get; set; } + public string ApiKey { get; set; } + public string OrganisationApiUrl { get; set; } + public bool EnableXRay { get; set; } + public RedisCacheSettings RedisCacheSettings { get; set; } + public MessageSettings MessageSettings { get; set; } + public ConnectionStrings ConnectionStrings { get; set; } + public EmailSettings EmailSettings { get; set; } + public WrapperApiSettings WrapperApiSettings { get; set; } + public NotificationValidationConfigurations NotificationValidationConfigurations { get; set; } + } + + public class RedisCacheSettings + { + public bool IsEnabled { get; set; } + } + + public class MessageSettings + { + public string ApiKey { get; set; } + public string TemplateId { get; set; } + } + + public class ConnectionStrings + { + public string CcsSso { get; set; } + } + + public class EmailSettings + { + public string ApiKey { get; set; } + } + + public class WrapperApiSettings + { + public string ConfigApiKey { get; set; } + public string ApiGatewayEnabledConfigUrl { get; set; } + public string ApiGatewayDisabledConfigUrl { get; set; } + } + + public class NotificationValidationConfigurations + { + public bool EnableValidation { get; set; } + public int SmsMsgLength { get; set; } + public int OrgNameLegnth { get; set; } + public string EmailRegex { get; set; } + public int FirstNameLength { get; set; } + public int LastNameLength { get; set; } + public List SignInProviders { get; set; } + public string LinkRegex { get; set; } + public string CcsMsg { get; set; } + } +} diff --git a/Ccs.Ppg.NotificationService/Model/EmailInfo.cs b/Ccs.Ppg.NotificationService/Model/EmailInfo.cs new file mode 100644 index 0000000..0a75f39 --- /dev/null +++ b/Ccs.Ppg.NotificationService/Model/EmailInfo.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Ccs.Ppg.NotificationService.Model +{ + public class EmailInfo + { + [Required(ErrorMessage = "Email is required Ex:UserName@example.com")] + public string To { get; set; } + + [Required(ErrorMessage = "Email template is required")] + public string TemplateId { get; set; } + + public Dictionary BodyContent { get; set; } + + } +} diff --git a/Ccs.Ppg.NotificationService/Model/ServiceRoleGroup.cs b/Ccs.Ppg.NotificationService/Model/ServiceRoleGroup.cs new file mode 100644 index 0000000..ead4de2 --- /dev/null +++ b/Ccs.Ppg.NotificationService/Model/ServiceRoleGroup.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Ccs.Ppg.NotificationService.Model +{ + public class ServiceRoleGroup + { + public int Id { get; set; } + + [JsonPropertyOrder(-1)] + public string Name { get; set; } + + [JsonPropertyOrder(-1)] + public string Key { get; set; } + + public int DisplayOrder { get; set; } + + public string Description { get; set; } + + public int[] AutoValidationRoleTypeEligibility { get; set; } = { }; + + public int ApprovalRequired { get; set; } = 0; + } +} diff --git a/Ccs.Ppg.NotificationService/Services/AwsParameterStoreService.cs b/Ccs.Ppg.NotificationService/Services/AwsParameterStoreService.cs index b396638..1ab6fb7 100644 --- a/Ccs.Ppg.NotificationService/Services/AwsParameterStoreService.cs +++ b/Ccs.Ppg.NotificationService/Services/AwsParameterStoreService.cs @@ -16,12 +16,25 @@ public class AwsParameterStoreService : IAwsParameterStoreService public AwsParameterStoreService() { - string env = Environment.GetEnvironmentVariable("VCAP_SERVICES", EnvironmentVariableTarget.Process); - var envData = (JObject)JsonConvert.DeserializeObject(env); - string setting = JsonConvert.SerializeObject(envData["user-provided"].FirstOrDefault(obj => obj["name"].Value().Contains("ssm-service"))); - _settings = JsonConvert.DeserializeObject(setting.ToString()); - var credentials = new BasicAWSCredentials(_settings.credentials.aws_access_key_id, _settings.credentials.aws_secret_access_key); - _client = new AmazonSimpleSystemsManagementClient(credentials, RegionEndpoint.GetBySystemName(_settings.credentials.region)); + string accessKeyId = Environment.GetEnvironmentVariable("ACCESSKEYID"); + string accessKeySecret = Environment.GetEnvironmentVariable("ACCESSKEYSECRET"); + string region = Environment.GetEnvironmentVariable("REGION"); + + // Deployed in cloud foundry then will not get credentials from environment variable + if (string.IsNullOrWhiteSpace(accessKeyId) || string.IsNullOrWhiteSpace(accessKeySecret) || string.IsNullOrWhiteSpace(region)) + { + string env = Environment.GetEnvironmentVariable("VCAP_SERVICES", EnvironmentVariableTarget.Process); + var envData = (JObject)JsonConvert.DeserializeObject(env); + string setting = JsonConvert.SerializeObject(envData["user-provided"].FirstOrDefault(obj => obj["name"].Value().Contains("ssm-service"))); + _settings = JsonConvert.DeserializeObject(setting.ToString()); + + accessKeyId = _settings.credentials.aws_access_key_id; + accessKeySecret = _settings.credentials.aws_secret_access_key; + region = _settings.credentials.region; + } + + var credentials = new BasicAWSCredentials(accessKeyId, accessKeySecret); + _client = new AmazonSimpleSystemsManagementClient(credentials, RegionEndpoint.GetBySystemName(region)); } public async Task> GetParameters(string path) diff --git a/Ccs.Ppg.NotificationService/Services/EmailProviderService.cs b/Ccs.Ppg.NotificationService/Services/EmailProviderService.cs new file mode 100644 index 0000000..cb43b22 --- /dev/null +++ b/Ccs.Ppg.NotificationService/Services/EmailProviderService.cs @@ -0,0 +1,191 @@ +using Ccs.Ppg.NotificationService.Model; +using Ccs.Ppg.NotificationService.Services.IServices; +using Ccs.Ppg.Utility.Constants.Constants; +using Ccs.Ppg.Utility.Exceptions.Exceptions; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Notify.Client; +using Notify.Models.Responses; +using System.Text.RegularExpressions; + +namespace Ccs.Ppg.NotificationService.Services +{ + public class EmailProviderService : IEmailProviderService + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly IWrapperConfigurationService _wrapperConfigurationService; + private readonly ApplicationConfigurationInfo _applicationConfigurationInfo; + + public EmailProviderService(IHttpClientFactory httpClientFactory, IWrapperConfigurationService wrapperConfigurationService, + ApplicationConfigurationInfo applicationConfigurationInfo) + { + _httpClientFactory = httpClientFactory; + _wrapperConfigurationService = wrapperConfigurationService; + _applicationConfigurationInfo = applicationConfigurationInfo; + } + public async Task SendEmailAsync(EmailInfo emailInfo) + { + try + { + //Adding log for testing purpose + Console.WriteLine("EmailInfo Properties:"); + Console.WriteLine($"To: {emailInfo.To}"); + Console.WriteLine($"TemplateId: {emailInfo.TemplateId}"); + if (emailInfo.BodyContent != null) + { + Console.WriteLine("BodyContent:"); + foreach (var kvp in emailInfo.BodyContent) + { + Console.WriteLine($" {kvp.Key}: {kvp.Value}"); + } + } + else + { + Console.WriteLine("BodyContent: (null)"); + } + + var bodyContent = new Dictionary(); + emailInfo.BodyContent.ToList().ForEach(pair => bodyContent.Add(pair.Key, pair.Value)); + bool isValidationEnbled = _applicationConfigurationInfo.NotificationValidationConfigurations.EnableValidation; + //Validate Email Headers + if (isValidationEnbled && !ValidateEmailHeaders(emailInfo)) + { + Console.WriteLine(ErrorConstant.ErrorInvalidDetails); + throw new CcsSsoException(ErrorConstant.ErrorInvalidDetails); + } + if (isValidationEnbled && !await ValidateEmailMessage(bodyContent)) + { + Console.WriteLine(ErrorConstant.ErrorInvalidDetails); + throw new CcsSsoException(ErrorConstant.ErrorInvalidDetails); + } + + var apiKey = _applicationConfigurationInfo.EmailSettings.ApiKey; + var client = _httpClientFactory.CreateClient(); + var httpClientWithProxy = new HttpClientWrapper(client); + var notificationClient = new NotificationClient(httpClientWithProxy, apiKey); + + EmailNotificationResponse response = await notificationClient.SendEmailAsync(emailInfo.To, emailInfo.TemplateId, bodyContent); + } + catch (CcsSsoException ex) + { + throw ex; + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw new Exception(ex.Message); + } + } + + private bool ValidateEmailHeaders(EmailInfo emailInfo) + { + if (!string.IsNullOrEmpty(emailInfo.To)) + { + var emailRegex = _applicationConfigurationInfo.NotificationValidationConfigurations.EmailRegex; + Regex re = new Regex(emailRegex); + if (!re.IsMatch(emailInfo.To)) + return false; + } + if (!string.IsNullOrEmpty(emailInfo.TemplateId)) + { + Guid result; + bool isValid = Guid.TryParse(emailInfo.TemplateId, out result); + if (!isValid) return false; + } + return true; + } + + public async Task ValidateEmailMessage(Dictionary msgBody) + { + foreach (var msg in msgBody) + { + switch (msg.Key.ToLower()) + { + case "orgname": + { + var orgNameLength = _applicationConfigurationInfo.NotificationValidationConfigurations.OrgNameLegnth; + if (!string.IsNullOrEmpty(msg.Value) && msg.Value.Length > orgNameLength) + return false; + break; + } + case "emailaddress": + case "emailid": + case "email": + { + var emailRegex = _applicationConfigurationInfo.NotificationValidationConfigurations.EmailRegex; + Regex re = new Regex(emailRegex); + if (!string.IsNullOrEmpty(msg.Value) && !re.IsMatch(msg.Value)) + return (false); + break; + } + case "firstname": + { + var firstNameLength = _applicationConfigurationInfo.NotificationValidationConfigurations.FirstNameLength; + if (!string.IsNullOrEmpty(msg.Value) && msg.Value.Length > firstNameLength) + return false; + break; + } + case "lastname": + { + var lastNameLength = _applicationConfigurationInfo.NotificationValidationConfigurations.LastNameLength; + if (!string.IsNullOrEmpty(msg.Value) && msg.Value.Length > lastNameLength) + return false; + break; + } + case "link": + case "mfaresetlink": + case "federatedlogin": + case "conclaveloginlink": + case "orgregistersationlink": + { + var linkRegex = _applicationConfigurationInfo.NotificationValidationConfigurations.LinkRegex; + Regex re = new Regex(linkRegex); + if (!string.IsNullOrEmpty(msg.Value) && !re.IsMatch(msg.Value)) + return (false); + break; + } + case "servicenames": + case "servicename": + { + if (!string.IsNullOrEmpty(msg.Value)) + { + var selectedServices = msg.Value.Split(','); + List delegatedServices = new List(); + foreach (var service in selectedServices) + { + delegatedServices.Add(service.Trim()); + } + var services = await _wrapperConfigurationService.GetServices(); + var result = delegatedServices.Except(services).ToList(); + if (result.Count > 0) return false; + } + break; + } + case "sigininproviders": + { + if (!string.IsNullOrEmpty(msg.Value)) + { + //validate against list of signin providers + var signinProviders = _applicationConfigurationInfo.NotificationValidationConfigurations.SignInProviders; + var selectedProviders = msg.Value.Split(','); + List providers = new List(); + providers.AddRange(selectedProviders); + var result = providers.Except(signinProviders).ToList(); + if (result.Count > 0) return false; + } + break; + } + case "ccsmsg": + { + string ccsMsg = _applicationConfigurationInfo.NotificationValidationConfigurations.CcsMsg; + if (!string.IsNullOrEmpty(msg.Value) && msg.Value != ccsMsg) + return false; + break; + } + + } + } + return true; + } + } +} \ No newline at end of file diff --git a/Ccs.Ppg.NotificationService/Services/IServices/IEmailProviderService.cs b/Ccs.Ppg.NotificationService/Services/IServices/IEmailProviderService.cs new file mode 100644 index 0000000..53622f4 --- /dev/null +++ b/Ccs.Ppg.NotificationService/Services/IServices/IEmailProviderService.cs @@ -0,0 +1,14 @@ +using Ccs.Ppg.NotificationService.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ccs.Ppg.NotificationService.Services.IServices +{ + public interface IEmailProviderService + { + Task SendEmailAsync(EmailInfo emailInfo); + } +} diff --git a/Ccs.Ppg.NotificationService/Services/IServices/IWrapperConfigurationService.cs b/Ccs.Ppg.NotificationService/Services/IServices/IWrapperConfigurationService.cs new file mode 100644 index 0000000..e5ce81c --- /dev/null +++ b/Ccs.Ppg.NotificationService/Services/IServices/IWrapperConfigurationService.cs @@ -0,0 +1,7 @@ +namespace Ccs.Ppg.NotificationService.Services.IServices +{ + public interface IWrapperConfigurationService + { + Task> GetServices(); + } +} diff --git a/Ccs.Ppg.NotificationService/Services/MessageProviderService.cs b/Ccs.Ppg.NotificationService/Services/MessageProviderService.cs index aef0259..c287edf 100644 --- a/Ccs.Ppg.NotificationService/Services/MessageProviderService.cs +++ b/Ccs.Ppg.NotificationService/Services/MessageProviderService.cs @@ -1,5 +1,7 @@ using Ccs.Ppg.NotificationService.Model; using Ccs.Ppg.NotificationService.Services.IServices; +using Ccs.Ppg.Utility.Constants.Constants; +using Ccs.Ppg.Utility.Exceptions.Exceptions; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using Notify.Client; @@ -10,32 +12,44 @@ namespace Ccs.Ppg.NotificationService.Services public class MessageProviderService : IMessageProviderService { private readonly IHttpClientFactory _httpClientFactory; - private readonly IConfiguration _configuration; + private readonly ApplicationConfigurationInfo _applicationConfigurationInfo; - public MessageProviderService(IHttpClientFactory httpClientFactory, IConfiguration configuration) + public MessageProviderService(IHttpClientFactory httpClientFactory, ApplicationConfigurationInfo applicationConfigurationInfo) { _httpClientFactory = httpClientFactory; - _configuration = configuration; + _applicationConfigurationInfo = applicationConfigurationInfo; } public async Task SendMessage(MessageRequestModel messageInfo) { try { - var apiKey = _configuration["Message:ApiKey"]; - var templateId = !string.IsNullOrEmpty(messageInfo.TemplateId) ? messageInfo.TemplateId : _configuration["Message:TemplateId"]; + // var data = new Dictionary { { "code", messageInfo.Message } }; + var data = new Dictionary(); + messageInfo.Message.ForEach(message => data.Add(message.key, message.Message)); + + Console.WriteLine("SmsInfo Properties:"); + foreach (var d in data) + { + Console.WriteLine($" {d.Key}: {d.Value}"); + } + + bool isValidationEnbled = _applicationConfigurationInfo.NotificationValidationConfigurations.EnableValidation; + if (isValidationEnbled && !ValidateMessage(messageInfo.Message?.FirstOrDefault())) + { + Console.WriteLine(ErrorConstant.ErrorInvalidDetails); + throw new CcsSsoException(ErrorConstant.ErrorInvalidDetails); + } + + var apiKey = _applicationConfigurationInfo.MessageSettings.ApiKey; + var templateId = !string.IsNullOrEmpty(messageInfo.TemplateId) ? messageInfo.TemplateId : _applicationConfigurationInfo.MessageSettings.TemplateId; var client = _httpClientFactory.CreateClient(); var httpClientWithProxy = new HttpClientWrapper(client); var notificationClient = new NotificationClient(httpClientWithProxy, apiKey); - // var data = new Dictionary { { "code", messageInfo.Message } }; - var data = new Dictionary(); - messageInfo.Message.ForEach(message => data.Add(message.key, message.Message)); - SmsNotificationResponse response = notificationClient.SendSms(mobileNumber: messageInfo.PhoneNumber, templateId: templateId, personalisation: data); return true; - } catch (Exception ex) { @@ -44,5 +58,16 @@ public async Task SendMessage(MessageRequestModel messageInfo) } } + + public bool ValidateMessage(MessageInfo msgInfo) + { + var msgLength = _applicationConfigurationInfo.NotificationValidationConfigurations.SmsMsgLength; + Console.WriteLine("Message OTP: " + msgInfo.Message); + if (msgInfo != null && msgInfo.Message.Length > msgLength) + { + return false; + } + return true; + } } } diff --git a/Ccs.Ppg.NotificationService/Services/WrapperConfigurationService.cs b/Ccs.Ppg.NotificationService/Services/WrapperConfigurationService.cs new file mode 100644 index 0000000..39302fc --- /dev/null +++ b/Ccs.Ppg.NotificationService/Services/WrapperConfigurationService.cs @@ -0,0 +1,38 @@ +using Amazon.Runtime.Internal.Endpoints.StandardLibrary; +using Ccs.Ppg.NotificationService.Services.IServices; +using Ccs.Ppg.NotificationService.Model; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using System.Text.Json.Serialization; + +namespace Ccs.Ppg.NotificationService.Services +{ + public class WrapperConfigurationService : IWrapperConfigurationService + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ApplicationConfigurationInfo _applicationConfigurationInfo; + + public WrapperConfigurationService(IHttpClientFactory httpClientFactory, ApplicationConfigurationInfo applicationConfigurationInfo) + { + _httpClientFactory = httpClientFactory; + _applicationConfigurationInfo = applicationConfigurationInfo; + } + + public async Task> GetServices() + { + List services = new List(); + var client = _httpClientFactory.CreateClient(); + client.BaseAddress = new Uri(_applicationConfigurationInfo.WrapperApiSettings.ApiGatewayEnabledConfigUrl); + client.DefaultRequestHeaders.Add("X-API-Key", _applicationConfigurationInfo.WrapperApiSettings.ConfigApiKey); + var response = await client.GetAsync("service-role-groups"); + var responseString = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + var result = JsonConvert.DeserializeObject>(responseString); + services.AddRange(result.Select(x => x.Name)); + } + return services; + } + } +} diff --git a/Ccs.Ppg.NotificationService/Startup.cs b/Ccs.Ppg.NotificationService/Startup.cs index 279768a..0103e3a 100644 --- a/Ccs.Ppg.NotificationService/Startup.cs +++ b/Ccs.Ppg.NotificationService/Startup.cs @@ -16,6 +16,8 @@ public static void AddServices(this IServiceCollection services, IConfiguration { services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } } diff --git a/Ccs.Ppg.Utility.Authorization/Ccs.Ppg.Utility.Authorization.csproj b/Ccs.Ppg.Utility.Authorization/Ccs.Ppg.Utility.Authorization.csproj index 2bd3bed..cd77164 100644 --- a/Ccs.Ppg.Utility.Authorization/Ccs.Ppg.Utility.Authorization.csproj +++ b/Ccs.Ppg.Utility.Authorization/Ccs.Ppg.Utility.Authorization.csproj @@ -13,12 +13,13 @@ - + + @@ -26,7 +27,7 @@ true all - + all diff --git a/Ccs.Ppg.Utility.Authorization/Startup/Policies/ClaimAuthorisationPolicyProvider.cs b/Ccs.Ppg.Utility.Authorization/Startup/Policies/ClaimAuthorisationPolicyProvider.cs index 2b03479..a9037b8 100644 --- a/Ccs.Ppg.Utility.Authorization/Startup/Policies/ClaimAuthorisationPolicyProvider.cs +++ b/Ccs.Ppg.Utility.Authorization/Startup/Policies/ClaimAuthorisationPolicyProvider.cs @@ -6,58 +6,63 @@ namespace Ccs.Ppg.Utility.Authorization { - public class ClaimAuthorisationPolicyProvider : IAuthorizationPolicyProvider + public class ClaimAuthorisationPolicyProvider : IAuthorizationPolicyProvider + { + public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } + + private readonly IHttpContextAccessor _httpContextAccessor; + + public ClaimAuthorisationPolicyProvider(IOptions options, IHttpContextAccessor httpContextAccessor) + { + FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); + _httpContextAccessor = httpContextAccessor; + } + + public async Task GetPolicyAsync(string policyName) + { + var xapiKey = _httpContextAccessor.HttpContext.Request.Headers["X-API-Key"]; + + if (!policyName.StartsWith(ClaimAuthoriseAttribute.POLICY_PREFIX)) + { + return await FallbackPolicyProvider.GetPolicyAsync(policyName); + } + + var policyBuilder = new AuthorizationPolicyBuilder(); + + var requestContext = _httpContextAccessor.HttpContext.RequestServices.GetService(); + + if (!string.IsNullOrEmpty(xapiKey)) // Requests with api key no authorization + { + policyBuilder.RequireAssertion(context => true); + } + else + { + ApplyAuthPolicy(policyBuilder, policyName); + } + + return await Task.FromResult(policyBuilder.Build()); + } + + public Task GetDefaultPolicyAsync() + { + return ((IAuthorizationPolicyProvider)FallbackPolicyProvider).GetDefaultPolicyAsync(); + } + + public Task GetFallbackPolicyAsync() { - public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } - - private readonly IHttpContextAccessor _httpContextAccessor; - - public ClaimAuthorisationPolicyProvider(IOptions options, IHttpContextAccessor httpContextAccessor) - { - FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); - _httpContextAccessor = httpContextAccessor; - } - - public async Task GetPolicyAsync(string policyName) - { - var xapiKey = _httpContextAccessor.HttpContext.Request.Headers["X-API-Key"]; - - if (policyName.StartsWith(ClaimAuthoriseAttribute.POLICY_PREFIX)) - { - var claimString = policyName.Substring(ClaimAuthoriseAttribute.POLICY_PREFIX.Length); - var claimList = claimString.Split(','); - var policyBuilder = new AuthorizationPolicyBuilder(); - - var requestContext = _httpContextAccessor.HttpContext.RequestServices.GetService(); - - if (!string.IsNullOrEmpty(xapiKey)) // Requests with api key no authorization - { - policyBuilder.RequireAssertion(context => true); - } - else - { - var authService = _httpContextAccessor.HttpContext.RequestServices.GetService(); - - policyBuilder.RequireAssertion(context => - { - return authService.AuthorizeUser(claimList); - }); - } - - return await Task.FromResult(policyBuilder.Build()); - } - - return await FallbackPolicyProvider.GetPolicyAsync(policyName); - } - - public Task GetDefaultPolicyAsync() - { - return ((IAuthorizationPolicyProvider)FallbackPolicyProvider).GetDefaultPolicyAsync(); - } - - public Task GetFallbackPolicyAsync() - { - return ((IAuthorizationPolicyProvider)FallbackPolicyProvider).GetFallbackPolicyAsync(); - } + return ((IAuthorizationPolicyProvider)FallbackPolicyProvider).GetFallbackPolicyAsync(); + } + + private void ApplyAuthPolicy(AuthorizationPolicyBuilder policyBuilder, string policyName) + { + var claimString = policyName.Substring(ClaimAuthoriseAttribute.POLICY_PREFIX.Length); + var claimList = claimString.Split(','); + var authService = _httpContextAccessor.HttpContext.RequestServices.GetService(); + + policyBuilder.RequireAssertion(context => + { + return authService.AuthorizeUser(claimList); + }); } + } } \ No newline at end of file diff --git a/Ccs.Ppg.Utility.Authorization/Startup/SessionAuthorization/SessionAuthorizationMiddleware.cs b/Ccs.Ppg.Utility.Authorization/Startup/SessionAuthorization/SessionAuthorizationMiddleware.cs index 1bd6d60..9124674 100644 --- a/Ccs.Ppg.Utility.Authorization/Startup/SessionAuthorization/SessionAuthorizationMiddleware.cs +++ b/Ccs.Ppg.Utility.Authorization/Startup/SessionAuthorization/SessionAuthorizationMiddleware.cs @@ -16,7 +16,7 @@ public class SessionAuthorizationMiddleware private readonly IConfiguration _config; private readonly ITokenService _tokenService; private readonly ICcsServiceCacheService _ccsServiceCacheService; - + private const string healthEndPoint = "health-check"; public SessionAuthorizationMiddleware(RequestDelegate next, IRedisCacheService redisCacheService, @@ -34,6 +34,14 @@ public SessionAuthorizationMiddleware(RequestDelegate next, public async Task Invoke(HttpContext context, RequestContext requestContext) { var success = await ValidateTokenOrApiKey(context, requestContext); + var path = context.Request.Path.Value.TrimStart('/').TrimEnd('/'); + + if (path.ToUpper() == healthEndPoint.ToUpper()) + { + context.Response.StatusCode = 200; + return; + } + if (success) await _next(context); } diff --git a/Ccs.Ppg.Utility.Cache/Ccs.Ppg.Utility.Cache.csproj b/Ccs.Ppg.Utility.Cache/Ccs.Ppg.Utility.Cache.csproj index d046290..fe93a73 100644 --- a/Ccs.Ppg.Utility.Cache/Ccs.Ppg.Utility.Cache.csproj +++ b/Ccs.Ppg.Utility.Cache/Ccs.Ppg.Utility.Cache.csproj @@ -12,9 +12,10 @@ - + - + + diff --git a/Ccs.Ppg.Utility.Cache/nuget.config b/Ccs.Ppg.Utility.Cache/nuget.config index e1ee084..faf9eb0 100644 --- a/Ccs.Ppg.Utility.Cache/nuget.config +++ b/Ccs.Ppg.Utility.Cache/nuget.config @@ -7,8 +7,8 @@ - - + + - \ No newline at end of file + diff --git a/Ccs.Ppg.Utility.Exceptions/Ccs.Ppg.Utility.Exceptions.csproj b/Ccs.Ppg.Utility.Exceptions/Ccs.Ppg.Utility.Exceptions.csproj index a6baef0..57e12cf 100644 --- a/Ccs.Ppg.Utility.Exceptions/Ccs.Ppg.Utility.Exceptions.csproj +++ b/Ccs.Ppg.Utility.Exceptions/Ccs.Ppg.Utility.Exceptions.csproj @@ -15,6 +15,7 @@ + diff --git a/Ccs.Ppg.Utility.Logging/Ccs.Ppg.Utility.Logging.csproj b/Ccs.Ppg.Utility.Logging/Ccs.Ppg.Utility.Logging.csproj index a23b293..708ecab 100644 --- a/Ccs.Ppg.Utility.Logging/Ccs.Ppg.Utility.Logging.csproj +++ b/Ccs.Ppg.Utility.Logging/Ccs.Ppg.Utility.Logging.csproj @@ -16,6 +16,7 @@ + diff --git a/Ccs.Ppg.Utility.Swagger/Ccs.Ppg.Utility.Swagger.csproj b/Ccs.Ppg.Utility.Swagger/Ccs.Ppg.Utility.Swagger.csproj index 27e1771..9cde056 100644 --- a/Ccs.Ppg.Utility.Swagger/Ccs.Ppg.Utility.Swagger.csproj +++ b/Ccs.Ppg.Utility.Swagger/Ccs.Ppg.Utility.Swagger.csproj @@ -18,7 +18,7 @@ - + <_PackageFiles Include="$(OutputPath)\*.dll"> diff --git a/Ccs.Ppg.Utility.Swagger/Startup.cs b/Ccs.Ppg.Utility.Swagger/Startup.cs index 6a26b61..9abe09f 100644 --- a/Ccs.Ppg.Utility.Swagger/Startup.cs +++ b/Ccs.Ppg.Utility.Swagger/Startup.cs @@ -38,8 +38,13 @@ public static void AddSwagger(this IServiceCollection services, string xmlPath, public static void ConfigureSwagger(this IApplicationBuilder app) { - app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwagger(c => + { + c.RouteTemplate = "notification-service/swagger/{documentname}/swagger.json"; + }); + app.UseSwaggerUI(c => { + c.RoutePrefix = "notification-service/swagger"; + }); } } } diff --git a/docker/notification.Dockerfile b/docker/notification.Dockerfile new file mode 100644 index 0000000..c599dad --- /dev/null +++ b/docker/notification.Dockerfile @@ -0,0 +1,9 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0.416-bookworm-slim AS NotificationAPI +WORKDIR /app +COPY . ./ +RUN dotnet restore ./Ccs.Ppg.NotificationService.API/Ccs.Ppg.NotificationService.API.csproj +COPY Ccs.Ppg.NotificationService.API/appsecrets.json /app/appsecrets.json +COPY Ccs.Ppg.NotificationService.API/appsettings.json /app/appsettings.json +RUN dotnet build --configuration Release ./Ccs.Ppg.NotificationService.API/Ccs.Ppg.NotificationService.API.csproj +EXPOSE 5000 +ENTRYPOINT ["dotnet","Ccs.Ppg.NotificationService.API/bin/Release/net6.0/Ccs.Ppg.NotificationService.API.dll"] diff --git a/ecs/notification.json b/ecs/notification.json new file mode 100644 index 0000000..22ba2d8 --- /dev/null +++ b/ecs/notification.json @@ -0,0 +1,94 @@ +{ + "family": "%SPACE%-notification", + "networkMode": "awsvpc", + "taskRoleArn": "arn:aws:iam::%AWS_ACCOUNT_ID%:role/%ENV_NAME%-ecs-task-execution-role", + "executionRoleArn": "arn:aws:iam::%AWS_ACCOUNT_ID%:role/%ENV_NAME%-ecs-task-execution-role", + "runtimePlatform": { + "operatingSystemFamily": "LINUX" + }, + "requiresCompatibilities": [ + "FARGATE" + ], + "cpu": "256", + "memory": "512", + "containerDefinitions": [ + { + "name": "%SPACE%-notification", + "image": "%AWS_ACCOUNT_ID%.dkr.ecr.eu-west-2.amazonaws.com/%SPACE%-notification:%SPACE%-api-notification-%BUILD_NUMBER%", + "cpu": 0, + "memoryReservation": 512, + "portMappings": [ + { + "containerPort": 5000, + "hostPort": 5000, + "protocol": "tcp" + } + ], + "essential": true, + "entryPoint": [], + "command": [], + "environment": [], + "secrets": [ + { + "name": "ACCESSKEYID", + "valueFrom": "ACCESSKEYID" + }, + { + "name": "ACCESSKEYSECRET", + "valueFrom": "ACCESSKEYSECRET" + }, + { + "name": "REGION", + "valueFrom": "REGION" + }, + { + "name": "STARTUP_URL", + "valueFrom": "STARTUP_URL" + } + ], + "mountPoints": [], + "volumesFrom": [], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/%SPACE%-notification", + "awslogs-region": "eu-west-2", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "xray-daemon", + "image": "public.ecr.aws/xray/aws-xray-daemon:alpha", + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "True", + "awslogs-group": "/ecs/ecs-cwagent-fargate", + "awslogs-region": "eu-west-2", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "cloudwatch-agent", + "image": "public.ecr.aws/cloudwatch-agent/cloudwatch-agent:latest", + "secrets": [ + { + "name": "CW_CONFIG_CONTENT", + "valueFrom": "ecs-cwagent" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-create-group": "True", + "awslogs-group": "/ecs/ecs-cwagent-fargate", + "awslogs-region": "eu-west-2", + "awslogs-stream-prefix": "ecs" + } + } + } + ] +} +