diff --git a/.azure/pipelines/pr/Phonebook.Backend.PictureService.pr.yml b/.azure/pipelines/pr/Phonebook.Backend.PictureService.pr.yml new file mode 100644 index 000000000..c93fece9e --- /dev/null +++ b/.azure/pipelines/pr/Phonebook.Backend.PictureService.pr.yml @@ -0,0 +1,23 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: none +pr: + branches: + include: + - master + paths: + include: + - /Phonebook.Backend/Phonebook.Backend.PictureService/* + + +pool: + vmImage: 'ubuntu-latest' + +steps: +- task: DotNetCoreCLI@2 + inputs: + command: 'build' + projects: '**/Phonebook.Backend.PictureService/*.csproj' diff --git a/Phonebook.Backend/.vscode/launch.json b/Phonebook.Backend/.vscode/launch.json new file mode 100644 index 000000000..811b91a40 --- /dev/null +++ b/Phonebook.Backend/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/bin/Debug/netcoreapp2.2/Phonebook.Backend.PictureService.dll", + "args": [], + "cwd": "${workspaceFolder}/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/Phonebook.Backend/.vscode/tasks.json b/Phonebook.Backend/.vscode/tasks.json new file mode 100644 index 000000000..aadef8d8c --- /dev/null +++ b/Phonebook.Backend/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/.gitignore b/Phonebook.Backend/Phonebook.Backend.PictureService/.gitignore new file mode 100644 index 000000000..2121e5df0 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/.gitignore @@ -0,0 +1,330 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +wwwroot/ +images/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.sln b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.sln new file mode 100644 index 000000000..69a563056 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29209.62 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Phonebook.Backend.PictureService", "Phonebook.Backend.PictureService\Phonebook.Backend.PictureService.csproj", "{E5EB1B88-030D-4CED-90E2-CEABB7812F7D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{39A3BB0B-64F3-4BC4-867F-937C99F1FAE4}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E5EB1B88-030D-4CED-90E2-CEABB7812F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5EB1B88-030D-4CED-90E2-CEABB7812F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5EB1B88-030D-4CED-90E2-CEABB7812F7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5EB1B88-030D-4CED-90E2-CEABB7812F7D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E575D074-EE97-4A69-AC86-2D40D72BAEAD} + EndGlobalSection +EndGlobal diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/BackgroundTasks/PurgeTask.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/BackgroundTasks/PurgeTask.cs new file mode 100644 index 000000000..502db72a0 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/BackgroundTasks/PurgeTask.cs @@ -0,0 +1,128 @@ +using KK.AspNetCore.BackgroundTasks.Scheduled; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Phonebook.Backend.PictureService.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Phonebook.Backend.PictureService.Controllers +{ + public class PurgeTask : IScheduledTask + { + + private readonly ILogger logger; + private readonly PictureServiceConfiguration configuration; + + public PurgeTask( + IScheduledTaskOptions options, + ILogger logger, + PictureServiceConfiguration configuration + ) + { + this.Options = options; + this.logger = logger; + this.configuration = configuration; + } + + public IScheduledTaskOptions Options { get; } + + public async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + var people = await GetPeople(); + var files = Directory.GetFiles(Path.Combine( + Directory.GetCurrentDirectory(), "images")); + + DeleteFilesOfNonMembers(files, people); + } + // Todo: check a more explicit error + catch (Exception err) + { + this.logger.LogError("ERROR: Purging images failed (might be a failed Request, maybe you should check the SSL Certificate)"); + } + + } + /// + /// Delete all files of non existing members + /// + /// all files that exist + /// all person that are members + private void DeleteFilesOfNonMembers(string[] files, Person[] persons) + { + foreach (string file in files) + { + string fileName = Path.GetFileNameWithoutExtension(file); + IEnumerable matches = persons.Where(p => + { + return p.id.ToLower() == fileName.ToLower(); + }); + + if (matches.Count() == 0) + { + try + { + File.Delete(file); + } + catch (Exception err) + { + this.logger.LogError("ERROR: Purging images failed (could not delete file)"); + }; + } + }; + this.logger.LogInformation("Purged all images from users not in database."); + } + /// + /// Try Get People from the PersonBackendUrl + /// + /// Get all people that are members + private async Task GetPeople() + { + HttpClientHandler clientHandler = new HttpClientHandler(); + // Ignore SSL Certificate Validity + if (this.configuration.IgnoreSSLValidity) + { + clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; }; + } + // The 'using' will help to prevent memory leaks. + // Create a new instance of HttpClient + using (HttpClient client = new HttpClient(clientHandler)) + { + client.Timeout = TimeSpan.FromSeconds(10); + //Setting up the response... + using (HttpResponseMessage res = await client.GetAsync(this.configuration.PersonBackendUrl)) + { + if (!res.IsSuccessStatusCode) + { + this.logger.LogError("ERROR: Purging images failed: API request failed"); + } + + using (HttpContent content = res.Content) + { + string data = await content.ReadAsStringAsync(); + if (data == null) + { + this.logger.LogError("ERROR: Purging images failed: No Users in API Response"); + } + Person[] persons = JsonConvert.DeserializeObject(data); + if (persons.Length == 0) + { + this.logger.LogError("ERROR: Purging images failed (Malformated response)"); + } + return persons; + } + } + } + } + } +} + +class Person +{ + public string id; +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Configuration/ContactInformation.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Configuration/ContactInformation.cs new file mode 100644 index 000000000..edc0fea08 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Configuration/ContactInformation.cs @@ -0,0 +1,21 @@ +using NetEscapades.Configuration.Validation; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace Phonebook.Backend.PictureService.Configuration +{ + public class ContactInformation : IValidatable + { + [Required] + public string Name { get; set; } + [Required] + public string Email { get; set; } + public void Validate() + { + Validator.ValidateObject(this, new ValidationContext(this), validateAllProperties: true); + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Configuration/PictureServiceConfiguration.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Configuration/PictureServiceConfiguration.cs new file mode 100644 index 000000000..1afd4db5c --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Configuration/PictureServiceConfiguration.cs @@ -0,0 +1,46 @@ +using System.ComponentModel.DataAnnotations; +using NetEscapades.Configuration.Validation; + +namespace Phonebook.Backend.PictureService.Configuration +{ + + /// + /// The application configuration model that contains all configuration options of the application. + /// + public class PictureServiceConfiguration : IValidatable + { + /// + /// Gets or sets the configuration for the CORS Allowed Hosts. + /// + public string[] AllowedCORSDomains { get; set; } = { }; + + /// + /// The Schedule in which Pictures will get deleted if no user could be found. + /// + /// Default: Once every day at startup -> May be executed multiple times a day depending on recycling + public string PurgeSchedule { get; set; } = "0 0 * * *"; + /// + /// The Url to the Person Endpoint of the Phonebook + /// Required in order to delete pictures of users who are not listed anymore. + /// + [Required] + [Url] + public string PersonBackendUrl { get; set; } + + /// + /// Ignore the validity of the SSL Certificate for the Person Endpoint + /// + public bool IgnoreSSLValidity { get; set; } = false; + + /// + /// Contact Information displayed in the swagger API description. + /// + [Required] + public ContactInformation ContactInformation { get; set; } + + public void Validate() + { + Validator.ValidateObject(this, new ValidationContext(this), validateAllProperties: true); + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/ConfigureSwaggerOptions.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/ConfigureSwaggerOptions.cs new file mode 100644 index 000000000..c27243bc0 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/ConfigureSwaggerOptions.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Phonebook.Backend.PictureService.Configuration; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Phonebook.Backend.PictureService +{ + /// + /// Configures the Swagger generation options. + /// + /// This allows API versioning to define a Swagger document per API version after the + /// service has been resolved from the service container. + public class ConfigureSwaggerOptions : IConfigureOptions + { + readonly IApiVersionDescriptionProvider provider; + private readonly PictureServiceConfiguration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The provider used to generate Swagger documents. + public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider, PictureServiceConfiguration configuration) + { + this.provider = provider; + this.configuration = configuration; + } + + /// + public void Configure(SwaggerGenOptions options) + { + // add a swagger document for each discovered API version + // note: you might choose to skip or document deprecated API versions differently + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); + } + } + + private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) + { + var info = new OpenApiInfo() + { + Title = "Picture Service API", + Version = description.ApiVersion.ToString(), + Description = "API for management of Phonebook Pictures", + Contact = new OpenApiContact() { Name = configuration.ContactInformation.Name, Email = configuration.ContactInformation.Email }, + TermsOfService = new Uri("https://example.org/"), + License = new OpenApiLicense() { Name = "MIT", Url = new Uri("https://github.com/T-Systems-MMS/phonebook/blob/master/LICENSE") } + }; + + if (description.IsDeprecated) + { + info.Description += " This API version has been deprecated."; + } + + return info; + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/EmployeePicturesController.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/EmployeePicturesController.cs new file mode 100644 index 000000000..fc12f043f --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/EmployeePicturesController.cs @@ -0,0 +1,162 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Cors; +using Phonebook.Backend.PictureService.Helpers; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Linq; + +namespace Phonebook.Backend.PictureService.Controllers +{ + [Authorize] + [EnableCors("AllowDomainList")] + [Route("/")] + [ApiVersion("2.0")] + [ApiVersion("1.0")] + [ApiController] + public class EmployeePictureController : ControllerBase + { + private static readonly string[] validContentTypes = new string[] + { + // JPEG + "image/jpeg", + // PNG + "image/png", + // BMP + "image/bmp", + "image/x-ms-bmp", + "image/x-bmp", + // SVG + "image/svg+xml", + // WebP + "image/webp", + // GIF + "image/gif", + // JPEG 2000 + "image/jp2", + "image/jpx", + "image/jpm", + // PSD + "image/vnd.adobe.photoshop", + "application/x-photoshop", + "application/photoshop", + "application/psd", + "image/psd", + // SGI + "image/sgi", + // TGA + "image/x-targa", + "image/x-tga", + // TIFF + "image/tiff", + "image/tiff-fx" + }; + + private readonly ILogger logger; + + public EmployeePictureController(ILogger logger) + { + this.logger = logger; + } + /// + /// Upload an employee picture. + /// + /// Content has to be formatted as "image/jpeg" + /// See to get the currently logged in user. + /// The id of the user. + /// A "multipart" + /// + /// A status code indicating the success of the action. + /// + /// Everyone can access this method. + /// Ok + /// No Payload + /// Content malformated + /// User not authorized + /// Internal Server Error + [HttpPost("{id}")] + [RequestSizeLimit(2147483648)] + public async Task UploadPicture(string id, IFormFile file) + { + if(file == null || file.Length == 0) + { + return BadRequest("No Payload."); + } + var user = HttpContext.User; + if(user.Identity.Name.Split("\\")[1] != id) + { + return StatusCode(401, "User not authorized."); + } + if(IsAValidContentType(file.ContentType)) + { + try + { + // Delete old File before writing the new one. + ImageHelperFunctions.DeleteFilesForUser(id); + + var path = Path.Combine( + Directory.GetCurrentDirectory(), "images", + id + Path.GetExtension(file.FileName)); + if(!Directory.Exists(Path.GetDirectoryName(path))) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + using(var stream = new FileStream(path, FileMode.OpenOrCreate)) + { + await file.CopyToAsync(stream); + } + + } + catch(Exception err) + { + this.logger.LogError(err, err.Message); + return StatusCode(500, "Internal Server Error"); + }; + + return Ok(); + } + else + { + // TODO: that is wrong! Maybe we should send a a list of supported mime types. + var AcceptableContentTypes = String.Join(",", validContentTypes); + return BadRequest($"Content malformated. Server does only accept {AcceptableContentTypes}"); + } + } + + /// + /// Delete a picture associated with a user id. + /// + /// The id of the user + /// A status code indicating the success of the action. + /// Ok + /// Ok, file not found + /// User not authorized + /// Internal Server Error + [HttpDelete("{id}")] + public IActionResult DeletePicture(string id) + { + var user = HttpContext.User; + if(user.Identity.Name.Split("\\")[1] != id) + { + return StatusCode(401, "User not authorized."); + } + try + { + return StatusCode((int)ImageHelperFunctions.DeleteFilesForUser(id)); + } + catch(Exception err) + { + this.logger.LogError(err, err.Message); + return StatusCode(500, "Internal Server Error"); + } + } + private bool IsAValidContentType(string ContentType) + { + return validContentTypes.Any(d => d == ContentType); + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/V1/UserController.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/V1/UserController.cs new file mode 100644 index 000000000..bb30d899c --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/V1/UserController.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Phonebook.Backend.PictureService.Controllers.V1 +{ + [Route("[controller]")] + [ApiController] + [Authorize] + [ApiVersion("1.0")] + public class UserController : ControllerBase + { + private readonly ILogger logger; + + public UserController(ILogger logger) + { + this.logger = logger; + } + /// + /// Displays the Username of the currently logged in User (via NTLM) + /// + /// Domain/Username + /// You have to be logged in via NTLM authentication + [HttpGet] + [Route("whoami")] + [ProducesResponseType(typeof(string), 200)] + public string WhoAmI() + { + var user = HttpContext.User; + logger.LogInformation($"Log user {user.Identity.Name}"); + return '"' + user.Identity.Name + '"'; + } + + } +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/V2/UserController.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/V2/UserController.cs new file mode 100644 index 000000000..bcd2ccba4 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Controllers/V2/UserController.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Phonebook.Backend.PictureService.Models; + +namespace Phonebook.Backend.PictureService.Controllers.V2 +{ + + [Route("[controller]")] + [ApiController] + [Authorize] + [ApiVersion("2.0")] + public class UserController : ControllerBase + { + private readonly ILogger logger; + + public UserController(ILogger logger) + { + this.logger = logger; + } + /// + /// Displays the Username of the currently logged in User (via NTLM) + /// + /// Domain/Username + /// You have to be logged in via NTLM authentication + [HttpGet] + [Route("whoami")] + public Identity WhoAmI() + { + var user = HttpContext.User; + logger.LogInformation($"Log user {user.Identity.Name}"); + var returnValue = new Identity(user.Identity.Name, Helpers.ImageHelperFunctions.DoesFileExist(user.Identity.Name.Split("\\")[1])); + return returnValue; + } + } +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Helpers/AdfsHelper.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Helpers/AdfsHelper.cs new file mode 100644 index 000000000..5972b6e94 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Helpers/AdfsHelper.cs @@ -0,0 +1,91 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.WsFederation; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Phonebook.Backend.PictureService.Helpers +{ + internal static class AdfsHelper + { + internal static void AddWsFederation(IServiceCollection services, IConfiguration configuration) + { + services.AddAuthentication(sharedOptions => + { + sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; + sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme; + + }) + .AddWsFederation(options => + { + // MetadataAddress represents the Active Directory instance used to authenticate users. + options.MetadataAddress = configuration.GetValue("WsFederationConfig:MetadataAddress"); ; + + var appId = configuration.GetValue("WsFederationConfig:ApplicationId"); + + + // Wtrealm is the app's identifier in the Active Directory instance. + // For ADFS, use the relying party's identifier, its WS-Federation Passive protocol URL: + options.Wtrealm = $"{appId}"; + + // maybe the following is requiered for azure. check this alter. + //options.Wtrealm = $"spn:{appId}"; + options.AllowUnsolicitedLogins = true; + // https://stackoverflow.com/questions/28627061/owin-ws-federation-setting-up-token-sliding-expiration + options.UseTokenLifetime = false; + + options.ClaimsIssuer = configuration.GetValue("WsFederationConfig:ClaimsIssuer"); + options.Events.OnRedirectToIdentityProvider = (c) => + { + var hostname = string.Empty; + if (string.IsNullOrWhiteSpace(c.Request.Headers["Referer"]) == false) + { + var uri = new Uri(c.Request.Headers["Referer"]); + hostname = $"{uri.Host}:{uri.Port}"; + } + else + { + hostname = c.Request.Host.ToString(); + } + c.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + c.ProtocolMessage.Wreply = $"https://{hostname}/signin-wsfed"; + c.Properties.RedirectUri = $"{c.Request.Headers["Referer"]}"; + c.Response.Headers.Add(new KeyValuePair("Location", new StringValues(c.ProtocolMessage.CreateSignInUrl()))); + c.HandleResponse(); + return Task.CompletedTask; + }; + + options.Events.OnAuthenticationFailed = (c) => + { + var hostname = string.Empty; + if (string.IsNullOrWhiteSpace(c.Request.Headers["Referer"]) == false) + { + var uri = new Uri(c.Request.Headers["Referer"]); + hostname = $"{uri.Host}:{uri.Port}"; + } + else + { + hostname = c.Request.Host.ToString(); + } + c.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + c.ProtocolMessage.Wreply = $"https://{hostname}/signin-wsfed"; + c.Properties.RedirectUri = $"{c.Request.Headers["Referer"]}"; + c.Response.Headers.Add(new KeyValuePair("Location", new StringValues(c.ProtocolMessage.CreateSignInUrl()))); + c.HandleResponse(); + return Task.CompletedTask; + }; + + // For AAD, use the App ID URI from the app registration's Properties blade: + //options.Wtrealm = "https://wsfedsample.onmicrosoft.com/63e4796f-9e4e-40f1-928b-d4efd0642d0d"; + + }) + .AddCookie(); + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Helpers/ImageHelperFunctions.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Helpers/ImageHelperFunctions.cs new file mode 100644 index 000000000..7a5de6908 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Helpers/ImageHelperFunctions.cs @@ -0,0 +1,102 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Phonebook.Backend.PictureService.Helpers +{ + /// + /// Contains functions to help with accessing and manage the images of the phonebook. + /// + public static class ImageHelperFunctions + { + /// + /// The default storage for the original images. + /// + private static string originalImagesFolder = Path.Combine(Directory.GetCurrentDirectory(), "images"); + + /// + /// Get all original images of the user from the storage. + /// + /// The user id. That is not the database id. It is more a unique shortname of a user. + private static Func> allOriginalUserImages = (userId) => Directory.EnumerateFiles(originalImagesFolder, userId + ".*"); + + /// + /// The folder for the generated images per user. + /// + /// The user id. That is not the database id. It is more a unique shortname of a user. + private static Func generatedImagesFolder = (userId) => Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "generated", userId); + + + + /// + /// + /// + /// The user id. That is not the database id. It is more a unique shortname of a user. + /// Why it's thrown. + /// If the user hasn't pictures uploaded yet you get an error. + /// + public static HttpStatusCode DeleteFilesForUser(string userId) + { + + if(DoesFileExist(userId) == false){ + throw new ArgumentException($"The user {userId} doesn't has a picture. Please check first if a user has pictures!"); + } + + var generatedPath = generatedImagesFolder(userId); + try + { + // Delete the generated Images Folder + if (Directory.Exists(generatedPath)) + { + Directory.Delete(generatedPath, true); + }; + + // Delete the uploaded file, ignoring the file ending + var files = allOriginalUserImages(userId); + if (files.Count() == 0) + { + return HttpStatusCode.NoContent; + } + + foreach (string file in files) + { + File.Delete(file); + }; + + return HttpStatusCode.NoContent; + } + catch (Exception err) + { + throw err; + }; + } + /// + /// Check if a picture exits in the storage. + /// + /// The user id. That is not the database id. It is more a unique shortname of a user. + /// True if the file exists. + public static Boolean DoesFileExist(string userId) + { + var uploadPath = Path.Combine( + Directory.GetCurrentDirectory(), "images"); + var files = allOriginalUserImages(userId); + try + { + // File does not exist + if (files.Count() == 1) + { + return true; + } + return false; + } + catch (Exception err) + { + return false; + }; + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Models/Identity.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Models/Identity.cs new file mode 100644 index 000000000..72625cff1 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Models/Identity.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Phonebook.Backend.PictureService.Models +{ + public class Identity + { + public string user { get; set; } + public bool hasPicture { get; set; } + public Identity(string user, bool hasPicture) + { + this.user = user; + this.hasPicture = hasPicture; + } + } +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.csproj b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.csproj new file mode 100644 index 000000000..54f2514e3 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService.csproj @@ -0,0 +1,36 @@ + + + + netcoreapp3.1 + + + + bin\Release\netcoreapp2.1\Phonebook.Backend.PictureService.xml + + + + bin\Debug\netcoreapp2.1\Phonebook.Backend.PictureService.xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Program.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Program.cs new file mode 100644 index 000000000..0e2583feb --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Phonebook.Backend.PictureService +{ + public class Program + { + public static void Main(string[] args) + { + var builder = CreateWebHostBuilder(args).Build(); + builder.Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Properties/launchSettings.json b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Properties/launchSettings.json new file mode 100644 index 000000000..b95970d0c --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Properties/launchSettings.json @@ -0,0 +1,40 @@ +{ + "iisSettings": { + "windowsAuthentication": true, + "anonymousAuthentication": true, + "iis": { + "applicationUrl": "http://localhost/Phonebook.Backend.PictureService", + "sslPort": 0 + }, + "iisExpress": { + "applicationUrl": "http://localhost:5000/", + "sslPort": 44300 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Phonebook.Backend.PictureService": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger/index.html", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44300;http://localhost:5000" + }, + "IIS": { + "commandName": "IIS", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Startup.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Startup.cs new file mode 100644 index 000000000..8131d3470 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/Startup.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; +using KK.AspNetCore.BackgroundTasks.Scheduled; +using KK.AspNetCore.Images.Processing; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Server.IISIntegration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Phonebook.Backend.PictureService.Configuration; +using Phonebook.Backend.PictureService.Controllers; +using Swashbuckle.AspNetCore.Swagger; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.Options; +using Swashbuckle.AspNetCore.SwaggerGen; +using Microsoft.Extensions.PlatformAbstractions; +using System.Reflection; +using static Phonebook.Backend.PictureService.Helpers.AdfsHelper; + +namespace Phonebook.Backend.PictureService +{ + /// + /// Startup Class + /// + public class Startup + { + private IConfiguration Configuration { get; } + private PictureServiceConfiguration AppSettings { get; } + private const string CorsPolicy = "AllowDomainList"; + /// + /// Initializes a new instance of the class. + /// + /// + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + this.AppSettings = configuration.Get(); + } + + /// + /// This method gets called by the runtime. Use this method to add services to the container. + /// + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + // Validation of the appsettings on Startup + services.AddSingleton(AppSettings); + services.UseConfigurationValidation(); + services.ConfigureValidatableSetting(Configuration); + services.ConfigureValidatableSetting(Configuration.GetSection("ContactInformation")); + services.AddSingleton(AppSettings); + + // Add Api Versioning Logic + services.AddApiVersioning(o => + { + o.ReportApiVersions = true; + o.AssumeDefaultVersionWhenUnspecified = true; + o.DefaultApiVersion = new ApiVersion(1, 0); + + }); + + services.AddVersionedApiExplorer(options => + { + // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service + // note: the specified format code will format the version as "'v'major[.minor][-status]" + options.GroupNameFormat = "'v'VVV"; + // note: this option is only necessary when versioning by url segment. the SubstitutionFormat + // can also be used to control the format of the API version in route templates + options.SubstituteApiVersionInUrl = true; + }); + + // Auth + +#if DEBUG + Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; +#endif + AddWsFederation(services, Configuration); + + services.AddImageProcessingSettings(Configuration); + + // Scheduled Tasks + services.AddSingleton>( + new ScheduledTaskOptions + { + Schedule = AppSettings.PurgeSchedule + } + ); + services.AddScheduledTask(); + services.AddHostedService(); + + + // Add Swagger + services.AddTransient, ConfigureSwaggerOptions>(); + services.AddSwaggerGen(c => + { + // add a custom operation filter which sets default values + c.OperationFilter(); + + + c.IncludeXmlComments(XmlCommentsFilePath); + }); + } + + /// + ///This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + ///The order should be like this: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1#middleware-order + /// + public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider) + { + //Should stay on Top + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + + // Show all Pictures in wwwroot + app.UseDirectoryBrowser(new DirectoryBrowserOptions + { + FileProvider = new PhysicalFileProvider( + Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")), + RequestPath = "" + }); + // Image Processing Stuff + app.UseImageProcessing(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), + // specifying the Swagger JSON endpoint. + app.UseSwagger(); + app.UseSwaggerUI(c => + { + foreach (var description in provider.ApiVersionDescriptions) + { + c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); + } + }); + + + } + + static string XmlCommentsFilePath + { + get + { + var basePath = PlatformServices.Default.Application.ApplicationBasePath; + var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml"; + return Path.Combine(basePath, fileName); + } + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/SwaggerDefaultValues.cs b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/SwaggerDefaultValues.cs new file mode 100644 index 000000000..723fe2bd2 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/SwaggerDefaultValues.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Swagger; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; + +namespace Phonebook.Backend.PictureService +{ + /// + /// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter. + /// + /// This is only required due to bugs in the . + /// Once they are fixed and published, this class can be removed. + public class SwaggerDefaultValues : IOperationFilter + { + /// + /// Applies the filter to the specified operation using the given context. + /// + /// The operation to apply the filter to. + /// The current operation filter context. + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var apiDescription = context.ApiDescription; + + operation.Deprecated |= apiDescription.IsDeprecated(); + + if (operation.Parameters == null) + { + return; + } + + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 + foreach (var parameter in operation.Parameters) + { + var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + if (parameter.Description == null) + { + parameter.Description = description.ModelMetadata?.Description; + } + + if (parameter.Schema.Default == null && description.DefaultValue != null) + { + parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString()); + } + + parameter.Required |= description.IsRequired; + } + } + } +} diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/appsettings.Development.json b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/appsettings.Development.json new file mode 100644 index 000000000..0f1187ed0 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/appsettings.Development.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "IgnoreSSLValidity": true, + "AllowedCORSDomains": [ + "*" + ] +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/appsettings.json b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/appsettings.json new file mode 100644 index 000000000..741e3c8de --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/appsettings.json @@ -0,0 +1,50 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + "AllowedCORSDomains": [], + "ContactInformation": { + "Name": "Phonebook Team", + "Email": "some Email" + }, + "PersonBackendUrl": "https://demo-phonebook.me/api/persons", + "ImageProcessing": { + "TargetFolder": "generated", + "SourceFolder": "images", + "LosslessCompress": true, + "OutputFormats": [ + { + "FileEndings": [ + "jpg", + "jpeg" + ] + } + ], + "Sizes": [ + { + "Width": 50, + "Quality": 80 + }, + { + "Width": 100, + "Quality": 80 + }, + { + "Width": 900, + "Quality": 95 + }, + { + "Width": 1800, + "Quality": 95 + } + ] + }, + "WsFederationConfig": { + "MetadataAddress": "https://login.microsoftonline.com/2f5af4bb-2ab0-4b54-87a0-9ce3ca95a9d0/federationmetadata/2007-06/federationmetadata.xml", + "ApplicationId": "63e4796f-9e4e-40f1-928b-d4efd0642d0d", + "ClaimsIssuer": "https://sts.windows.net/2f5af4bb-2ab0-4b54-87a0-9ce3ca95a9d0/" + } +} \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/web.config b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/web.config new file mode 100644 index 000000000..6c2fac719 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/web.config @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/wwwroot/.gitkeep b/Phonebook.Backend/Phonebook.Backend.PictureService/Phonebook.Backend.PictureService/wwwroot/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/Phonebook.Backend/Phonebook.Backend.PictureService/Readme.md b/Phonebook.Backend/Phonebook.Backend.PictureService/Readme.md new file mode 100644 index 000000000..f6d74b854 --- /dev/null +++ b/Phonebook.Backend/Phonebook.Backend.PictureService/Readme.md @@ -0,0 +1,21 @@ +# Picture Service API + +This project aims to host pictures. It also provides a self service for changing the pictures or deleting them. + +## Documentation + +The API is documented by Swagger. + +## Getting Started + +This is a DotNetCore2.1 project. Please install the [DotNetCore2.1-SDK](https://www.microsoft.com/net/download/windows). +The recommended IDE is [Visual Studio](https://visualstudio.microsoft.com/de/downloads/). + +## Development + +### Debugging with Postman (Authenticated) + +1. Configure Internet Options as shown [here](https://stackoverflow.com/a/44910453) and [here](https://stackoverflow.com/a/47749312) + - Use a Domain: `example.com` + - Use your PC name: for Example: `THISPCNAME` + - Fill in your AD Password and username: `username` (Your Windows account without the domain `DOMAIN\User`) diff --git a/Phonebook.Source.PeopleSoft/src/Phonebook.Source.PeopleSoft/Properties/launchSettings.json b/Phonebook.Source.PeopleSoft/src/Phonebook.Source.PeopleSoft/Properties/launchSettings.json index 2d23db381..80c55e273 100644 --- a/Phonebook.Source.PeopleSoft/src/Phonebook.Source.PeopleSoft/Properties/launchSettings.json +++ b/Phonebook.Source.PeopleSoft/src/Phonebook.Source.PeopleSoft/Properties/launchSettings.json @@ -34,4 +34,4 @@ "useSSL": true } } -} \ No newline at end of file +}