From 69dbba322f6ef275134b7b214510e47b9fcdd0b0 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Tue, 17 Nov 2020 15:11:40 +0200 Subject: IMplemented NSwag for DataStore WebAPI controller. Implemented data store tool dsUtil. --- .../Controllers/DataStoreController.cs | 114 +++++++++++++++-- .../Filters/JwtWebApiTokenFilter.cs | 72 +++++++++++ .../Nswag/DataStoreClient.nswag | 140 +++++++++++++++++++++ .../Tango.MachineService.csproj | 2 + 4 files changed, 321 insertions(+), 7 deletions(-) create mode 100644 Software/Visual_Studio/Web/Tango.MachineService/Filters/JwtWebApiTokenFilter.cs create mode 100644 Software/Visual_Studio/Web/Tango.MachineService/Nswag/DataStoreClient.nswag (limited to 'Software/Visual_Studio/Web') diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DataStoreController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DataStoreController.cs index f0dc0f2ba..0d35bd776 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DataStoreController.cs +++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DataStoreController.cs @@ -3,17 +3,36 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Security.Authentication; using System.Web.Http; +using Tango.BL.Builders; using Tango.BL.Entities; +using Tango.BL.Enumerations; +using Tango.Core.Cryptography; using Tango.DataStore; using Tango.DataStore.EF; using Tango.DataStore.Web; +using Tango.MachineService.Filters; +using Tango.Web.Controllers; using Tango.Web.Helpers; +using Tango.Web.Security; +using static Tango.MachineService.Controllers.DataStoreController; namespace Tango.MachineService.Controllers { - public class DataStoreController : ApiController + public class DataStoreController : TangoController { + public class TokenObject + { + public String UserGuid { get; set; } + public List Permissions { get; set; } + + public TokenObject() + { + Permissions = new List(); + } + } + private IDataStoreManager _manager; public DataStoreController() @@ -21,10 +40,57 @@ namespace Tango.MachineService.Controllers _manager = new EFDataStoreManager(); } + [HttpPost] + public LoginResponse Login(LoginRequest request) + { + User user = null; + + IHashGenerator hash = new BasicHashGenerator(); + var password = hash.Encrypt(request.Password); + + using (var db = ObservablesContextHelper.CreateContext()) + { + user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower() && x.Password == password).WithRolesAndPermissions().WithDeleted().Build(); + + if (user == null) + { + throw new AuthenticationException("Invalid email or password."); + } + + if (user.Deleted) + { + throw new AuthenticationException("Your account has been disabled. Please contact your administrator."); + } + + var token = WebToken.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject() + { + UserGuid = user.Guid, + Permissions = user.Permissions.Select(x => (Permissions)x.Code).ToList() + }, DateTime.UtcNow.AddDays(1)); + + return new LoginResponse() + { + Token = token.AccessToken, + ExpirationUTC = token.Expiration.Value, + }; + } + } + + [JwtWebApiTokenFilter] public List Get(String sn = null, String collection = null, String key = null) { try { + if (!RequestToken.Object.Permissions.Contains(Permissions.FSE_DataStoreRead)) + { + throw CreateHttpException(new AuthenticationException("The current user was not authorized to read from the data store."), HttpStatusCode.Unauthorized); + } + + if (key != null && collection == null) + { + throw CreateHttpException(new ArgumentException(), HttpStatusCode.BadRequest, "When specifying a key, collection must be specified."); + } + ValidateCollectionAndKey(collection, key); using (var db = ObservablesContextHelper.CreateContext()) @@ -35,12 +101,17 @@ namespace Tango.MachineService.Controllers if (machineGuid == null) { - return ThrowException>(new KeyNotFoundException(), HttpStatusCode.NotFound, "The specified machine serial number could not be found."); + throw CreateHttpException(new KeyNotFoundException(), HttpStatusCode.NotFound, "The specified machine serial number could not be found."); } var localItems = db.DataStoreItems.Where(x => !x.IsDeleted).Where(x => x.MachineGuid == machineGuid && (collection == null || x.CollectionName == collection) && (key == null || x.Key == key)).ToList(); var globalItems = db.GlobalDataStoreItems.Where(x => (collection == null || x.CollectionName == collection) && (key == null || x.Key == key)).ToList(); + if (localItems.Count == 0 && globalItems.Count == 0 && key != null) + { + throw CreateHttpException(new KeyNotFoundException(), HttpStatusCode.NotFound, "The specified key was not found on the data store."); + } + List finalList = new List(); foreach (var localItem in localItems) @@ -64,16 +135,31 @@ namespace Tango.MachineService.Controllers } } } + catch (HttpResponseException ex) + { + throw ex; + } catch (Exception ex) { - return ThrowException>(ex, HttpStatusCode.InternalServerError, ex.FlattenMessage()); + throw CreateHttpException(ex, HttpStatusCode.InternalServerError); } } + [JwtWebApiTokenFilter] public void Put([FromBody]DataStoreWebPutItem item) { try { + if (!RequestToken.Object.Permissions.Contains(Permissions.FSE_DataStoreWrite)) + { + throw CreateHttpException(new AuthenticationException("The current user was not authorized to write to the data store."), HttpStatusCode.BadRequest); + } + + if (item.Collection == null || item.Key == null) + { + throw CreateHttpException(new AuthenticationException("Collection and key must be specified."), HttpStatusCode.BadRequest); + } + ValidateCollectionAndKey(item.Collection, item.Key); using (var db = ObservablesContextHelper.CreateContext()) @@ -84,13 +170,18 @@ namespace Tango.MachineService.Controllers if (machineGuid == null) { - ThrowException>(new KeyNotFoundException(), HttpStatusCode.NotFound, "The specified machine serial number could not be found."); + throw CreateHttpException(new KeyNotFoundException("The specified machine serial number could not be found."), HttpStatusCode.NotFound); } DataStoreItem dbItem = db.DataStoreItems.FirstOrDefault(x => x.CollectionName == item.Collection && x.Key == item.Key); if (dbItem == null) { + if (!RequestToken.Object.Permissions.Contains(Permissions.FSE_DataStoreCreate)) + { + throw CreateHttpException(new AuthenticationException("The current user was not authorized to create new items on the data store."), HttpStatusCode.Unauthorized); + } + dbItem = new DataStoreItem(); dbItem.Key = item.Key; dbItem.CollectionName = item.Collection; @@ -110,6 +201,11 @@ namespace Tango.MachineService.Controllers if (dbItem == null) { + if (!RequestToken.Object.Permissions.Contains(Permissions.FSE_DataStoreCreate)) + { + throw CreateHttpException(new AuthenticationException("The current user was not authorized to create new items on the data store."), HttpStatusCode.Unauthorized); + } + dbItem = new GlobalDataStoreItem(); dbItem.Key = item.Key; dbItem.CollectionName = item.Collection; @@ -124,15 +220,19 @@ namespace Tango.MachineService.Controllers db.SaveChanges(); } } + catch (HttpResponseException ex) + { + throw ex; + } catch (Exception ex) { - ThrowException>(ex, HttpStatusCode.InternalServerError, ex.FlattenMessage()); + throw CreateHttpException(ex, HttpStatusCode.InternalServerError); } } - private T ThrowException(Exception ex, HttpStatusCode code, String message = null) + private HttpResponseException CreateHttpException(Exception ex, HttpStatusCode code, String message = null) { - throw new HttpResponseException(new HttpResponseMessage(code) + return new HttpResponseException(new HttpResponseMessage(code) { Content = new StringContent(message != null ? message : ex.Message), ReasonPhrase = ex.FlattenMessage() diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Filters/JwtWebApiTokenFilter.cs b/Software/Visual_Studio/Web/Tango.MachineService/Filters/JwtWebApiTokenFilter.cs new file mode 100644 index 000000000..89169fc48 --- /dev/null +++ b/Software/Visual_Studio/Web/Tango.MachineService/Filters/JwtWebApiTokenFilter.cs @@ -0,0 +1,72 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Authentication; +using System.Web; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Tango.Transport.Web; +using Tango.Web.Security; + +namespace Tango.MachineService.Filters +{ + public class JwtWebApiTokenFilter : ActionFilterAttribute + { + public bool AllowExpired { get; private set; } + + public JwtWebApiTokenFilter() + { + + } + + public JwtWebApiTokenFilter(bool allowExpired) + { + AllowExpired = allowExpired; + } + + public override void OnActionExecuting(HttpActionContext actionContext) + { + try + { + var authorizationHeader = actionContext.Request.Headers.Authorization; + + if (authorizationHeader != null && authorizationHeader.Parameter != null) + { + try + { + WebToken.Validate(MachineServiceConfig.JWT_TOKEN_SECRET, authorizationHeader.Parameter); + } + catch (JWT.TokenExpiredException) + { + if (!AllowExpired) + { + throw new TokenExpiredException("Token expired."); + } + } + catch (JWT.SignatureVerificationException) + { + throw new InvalidTokenException("Invalid token."); + } + } + else + { + throw new AuthenticationException("No token specified."); + } + } + catch (Exception ex) + { + throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized) + { + Content = new StringContent(ex.Message), + ReasonPhrase = ex.FlattenMessage() + }); + } + + base.OnActionExecuting(actionContext); + } + } +} \ No newline at end of file diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Nswag/DataStoreClient.nswag b/Software/Visual_Studio/Web/Tango.MachineService/Nswag/DataStoreClient.nswag new file mode 100644 index 000000000..c7aeb10a3 --- /dev/null +++ b/Software/Visual_Studio/Web/Tango.MachineService/Nswag/DataStoreClient.nswag @@ -0,0 +1,140 @@ +{ + "runtime": "Default", + "defaultVariables": "", + "documentGenerator": { + "webApiToOpenApi": { + "controllerNames": [ + "Tango.MachineService.Controllers.DataStoreController" + ], + "isAspNetCore": false, + "resolveJsonOptions": false, + "defaultUrlTemplate": "api/{controller}/{action}", + "addMissingPathParameters": false, + "includedVersions": null, + "defaultPropertyNameHandling": "Default", + "defaultReferenceTypeNullHandling": "Null", + "defaultDictionaryValueReferenceTypeNullHandling": "NotNull", + "defaultResponseReferenceTypeNullHandling": "NotNull", + "defaultEnumHandling": "Integer", + "flattenInheritanceHierarchy": false, + "generateKnownTypes": true, + "generateEnumMappingDescription": false, + "generateXmlObjects": false, + "generateAbstractProperties": false, + "generateAbstractSchemas": true, + "ignoreObsoleteProperties": false, + "allowReferencesWithProperties": false, + "excludedTypeNames": [], + "serviceHost": null, + "serviceBasePath": null, + "serviceSchemes": [], + "infoTitle": "My Title", + "infoDescription": null, + "infoVersion": "1.0.0", + "documentTemplate": null, + "documentProcessorTypes": [], + "operationProcessorTypes": [], + "typeNameGeneratorType": null, + "schemaNameGeneratorType": null, + "contractResolverType": null, + "serializerSettingsType": null, + "useDocumentProvider": true, + "documentName": "v1", + "aspNetCoreEnvironment": null, + "createWebHostBuilderMethod": null, + "startupType": null, + "allowNullableBodyParameters": true, + "output": null, + "outputType": "Swagger2", + "assemblyPaths": [ + "$(assembly)" + ], + "assemblyConfig": null, + "referencePaths": [], + "useNuGetCache": false + } + }, + "codeGenerators": { + "openApiToCSharpClient": { + "clientBaseClass": null, + "configurationClass": null, + "generateClientClasses": true, + "generateClientInterfaces": false, + "injectHttpClient": true, + "disposeHttpClient": true, + "protectedMethods": [], + "generateExceptionClasses": true, + "exceptionClass": "ApiException", + "wrapDtoExceptions": true, + "useHttpClientCreationMethod": false, + "httpClientType": "System.Net.Http.HttpClient", + "useHttpRequestMessageCreationMethod": false, + "useBaseUrl": true, + "generateBaseUrlProperty": true, + "generateSyncMethods": true, + "exposeJsonSerializerSettings": false, + "clientClassAccessModifier": "public", + "typeAccessModifier": "public", + "generateContractsOutput": false, + "contractsNamespace": null, + "contractsOutputFilePath": null, + "parameterDateTimeFormat": "s", + "parameterDateFormat": "yyyy-MM-dd", + "generateUpdateJsonSerializerSettingsMethod": true, + "useRequestAndResponseSerializationSettings": false, + "serializeTypeInformation": false, + "queryNullValue": "", + "className": "DataStoreClient", + "operationGenerationMode": "MultipleClientsFromOperationId", + "additionalNamespaceUsages": [], + "additionalContractNamespaceUsages": [], + "generateOptionalParameters": false, + "generateJsonMethods": false, + "enforceFlagEnums": false, + "parameterArrayType": "System.Collections.Generic.IEnumerable", + "parameterDictionaryType": "System.Collections.Generic.IDictionary", + "responseArrayType": "System.Collections.Generic.ICollection", + "responseDictionaryType": "System.Collections.Generic.IDictionary", + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "namespace": "Tango.DataStore.Web", + "requiredPropertiesMustBeDefined": true, + "dateType": "System.DateTimeOffset", + "jsonConverters": null, + "anyType": "object", + "dateTimeType": "System.DateTimeOffset", + "timeType": "System.TimeSpan", + "timeSpanType": "System.TimeSpan", + "arrayType": "System.Collections.Generic.ICollection", + "arrayInstanceType": "System.Collections.ObjectModel.Collection", + "dictionaryType": "System.Collections.Generic.IDictionary", + "dictionaryInstanceType": "System.Collections.Generic.Dictionary", + "arrayBaseType": "System.Collections.ObjectModel.Collection", + "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "classStyle": "Poco", + "generateDefaultValues": true, + "generateDataAnnotations": true, + "excludedTypeNames": [], + "excludedParameterNames": [], + "handleReferences": false, + "generateImmutableArrayProperties": false, + "generateImmutableDictionaryProperties": false, + "jsonSerializerSettingsTransformationMethod": null, + "inlineNamedArrays": false, + "inlineNamedDictionaries": false, + "inlineNamedTuples": true, + "inlineNamedAny": false, + "generateDtoTypes": true, + "generateOptionalPropertiesAsNullable": false, + "templateDirectory": null, + "typeNameGeneratorType": null, + "propertyNameGeneratorType": null, + "enumNameGeneratorType": null, + "serviceHost": null, + "serviceSchemes": null, + "output": "$(output)" + } + } +} \ No newline at end of file diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj b/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj index df4e346f6..bccd84cad 100644 --- a/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj +++ b/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj @@ -322,6 +322,7 @@ + @@ -342,6 +343,7 @@ + -- cgit v1.3.1