This commit is contained in:
2022-06-15 17:46:22 +00:00
parent bd24caca38
commit ba7344df69
4 changed files with 385 additions and 5 deletions

View File

@@ -0,0 +1,136 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using AyaNova.Models;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Biz;
namespace AyaNova.Api.Controllers
{
[ApiController]
[ApiVersion("8.0")]
[Route("api/v{version:apiVersion}/integration")]
[Produces("application/json")]
[Authorize]
public class IntegrationController : ControllerBase
{
private readonly AyContext ct;
private readonly ILogger<IntegrationController> log;
private readonly ApiServerState serverState;
/// <summary>
/// ctor
/// </summary>
/// <param name="dbcontext"></param>
/// <param name="logger"></param>
/// <param name="apiServerState"></param>
public IntegrationController(AyContext dbcontext, ILogger<IntegrationController> logger, ApiServerState apiServerState)
{
ct = dbcontext;
log = logger;
serverState = apiServerState;
}
/// <summary>
/// Create Integration
/// </summary>
/// <param name="newObject"></param>
/// <param name="apiVersion">From route path</param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> PostIntegration([FromBody] Integration newObject, ApiVersion apiVersion)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
Integration o = await biz.CreateAsync(newObject);
if (o == null)
return BadRequest(new ApiErrorResponse(biz.Errors));
else
return CreatedAtAction(nameof(IntegrationController.GetIntegration), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
}
/// <summary>
/// Get Integration
/// </summary>
/// <param name="id"></param>
/// <returns>Integration</returns>
[HttpGet("{id}")]
public async Task<IActionResult> GetIntegration([FromRoute] long id)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
var o = await biz.GetAsync(id, true, true);
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
return Ok(ApiOkResponse.Response(o));
}
/// <summary>
/// Update Integration
/// </summary>
/// <param name="updatedObject"></param>
/// <returns></returns>
[HttpPut]
public async Task<IActionResult> PutIntegration([FromBody] Integration updatedObject)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
var o = await biz.PutAsync(updatedObject);
if (o == null)
{
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
return StatusCode(409, new ApiErrorResponse(biz.Errors));
else
return BadRequest(new ApiErrorResponse(biz.Errors));
}
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
}
/// <summary>
/// Delete Integration
/// </summary>
/// <param name="id"></param>
/// <returns>NoContent</returns>
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteIntegration([FromRoute] long id)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
if (!await biz.DeleteAsync(id))
return BadRequest(new ApiErrorResponse(biz.Errors));
return NoContent();
}
//------------
}//eoc
}//eons

View File

@@ -1043,14 +1043,65 @@ namespace AyaNova.Biz
});
////////////////////////////////////////////////////////////
//INTEGRATION - note this is for the management UI
//for *all* integrations. Separately, each integration app
//will have it's own authorization system
//INTEGRATION
// (everyone but outside users Customer and HO)
//this right is for the integration data itself, NOT any other AyaNova data
//so if someone is malicious the worst case scenario is they can mess up the integration data
// but they would still need rights to access any AyaNova data under their account so there is no loophole here
// technically an integration may be used by any role user
// however not likely to be read only or limited rights rols
// so will allow full access for any user and leave
// finer tuning of authorization to integrating app itself
// Also, integration is only used to store app data conveniently it in no way is required to
// write api accessing apps so any limitations are not preventing 3rd parties from writing AyaNova api consuming apps of any kind
//
roles.Add(AyaType.Integration, new BizRoleSet()
{
Change = AuthorizationRoles.BizAdmin,
Change = AuthorizationRoles.BizAdminRestricted
| AuthorizationRoles.BizAdmin
| AuthorizationRoles.ServiceRestricted
| AuthorizationRoles.Service
| AuthorizationRoles.InventoryRestricted
| AuthorizationRoles.Inventory
| AuthorizationRoles.Accounting
| AuthorizationRoles.TechRestricted
| AuthorizationRoles.Tech
| AuthorizationRoles.SubContractorRestricted
| AuthorizationRoles.SubContractor
| AuthorizationRoles.Sales
| AuthorizationRoles.SalesRestricted
| AuthorizationRoles.OpsAdminRestricted
| AuthorizationRoles.OpsAdmin,
ReadFullRecord = AuthorizationRoles.BizAdminRestricted
| AuthorizationRoles.BizAdmin
| AuthorizationRoles.ServiceRestricted
| AuthorizationRoles.Service
| AuthorizationRoles.InventoryRestricted
| AuthorizationRoles.Inventory
| AuthorizationRoles.Accounting
| AuthorizationRoles.TechRestricted
| AuthorizationRoles.Tech
| AuthorizationRoles.SubContractorRestricted
| AuthorizationRoles.SubContractor
| AuthorizationRoles.Sales
| AuthorizationRoles.SalesRestricted
| AuthorizationRoles.OpsAdminRestricted
| AuthorizationRoles.OpsAdmin,
Select = AuthorizationRoles.BizAdminRestricted
| AuthorizationRoles.BizAdmin
| AuthorizationRoles.ServiceRestricted
| AuthorizationRoles.Service
| AuthorizationRoles.InventoryRestricted
| AuthorizationRoles.Inventory
| AuthorizationRoles.Accounting
| AuthorizationRoles.TechRestricted
| AuthorizationRoles.Tech
| AuthorizationRoles.SubContractorRestricted
| AuthorizationRoles.SubContractor
| AuthorizationRoles.Sales
| AuthorizationRoles.SalesRestricted
| AuthorizationRoles.OpsAdminRestricted
| AuthorizationRoles.OpsAdmin,
});
////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,193 @@
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using AyaNova.Util;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Models;
namespace AyaNova.Biz
{
internal class IntegrationBiz : BizObject
{
internal IntegrationBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
{
ct = dbcontext;
UserId = currentUserId;
UserTranslationId = userTranslationId;
CurrentUserRoles = UserRoles;
BizType = AyaType.Integration;
}
internal static IntegrationBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null)
{
if (httpContext != null)
return new IntegrationBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
else
return new IntegrationBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//EXISTS
internal async Task<bool> ExistsAsync(long id)
{
return await ct.Integration.AnyAsync(z => z.Id == id);
}
////////////////////////////////////////////////////////////////////////////////////////////////
//CREATE
//
internal async Task<Integration> CreateAsync(Integration newObject)
{
await ValidateAsync(newObject, null);
if (HasErrors)
return null;
else
{
await ct.Integration.AddAsync(newObject);
await ct.SaveChangesAsync();
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
return newObject;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
//GET
//
internal async Task<Integration> GetAsync(long id, bool logTheGetEvent = true, bool populatePartNames = false)
{
var ret = await ct.Integration.AsNoTracking().Include(z => z.Items).SingleOrDefaultAsync(m => m.Id == id);
if (logTheGetEvent && ret != null)
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct);
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//UPDATE
//
internal async Task<Integration> PutAsync(Integration putObject)
{
//Get the db object with no tracking as about to be replaced not updated
Integration dbObject = await GetAsync(putObject.Id, false);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND, "id");
return null;
}
if (dbObject.Concurrency != putObject.Concurrency)
{
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await ValidateAsync(putObject, dbObject);
if (HasErrors) return null;
ct.Replace(dbObject, putObject);
try
{
await ct.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!await ExistsAsync(putObject.Id))
AddError(ApiErrorCode.NOT_FOUND);
else
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
return null;
}
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct);
return putObject;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> DeleteAsync(long id)
{
using (var transaction = await ct.Database.BeginTransactionAsync())
{
Integration dbObject = await GetAsync(id, false);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
ValidateCanDelete(dbObject);
if (HasErrors)
return false;
ct.Integration.Remove(dbObject);
await ct.SaveChangesAsync();
//Log event
await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct);
await transaction.CommitAsync();
return true;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
//VALIDATION
//
private async Task ValidateAsync(Integration proposedObj, Integration currentObj)
{
bool isNew = currentObj == null;
//Name required
if (string.IsNullOrWhiteSpace(proposedObj.Name))
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name");
//If name is otherwise OK, check that name is unique
if (!PropertyHasErrors("Name"))
{
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
if (await ct.Integration.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id))
{
AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
}
}
//Name required
if (proposedObj.IntegrationAppId == Guid.Empty)
AddError(ApiErrorCode.VALIDATION_REQUIRED, "IntegrationAppId");
//If name is otherwise OK, check that name is unique
if (!PropertyHasErrors("IntegrationAppId"))
{
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
if (await ct.Integration.AnyAsync(m => m.IntegrationAppId == proposedObj.IntegrationAppId && m.Id != proposedObj.Id))
{
AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "IntegrationAppId");
}
}
}
private void ValidateCanDelete(Integration inObj)
{
//whatever needs to be check to delete this object
}
/////////////////////////////////////////////////////////////////////
}//eoc
}//eons

View File

@@ -14,7 +14,7 @@ namespace AyaNova.Models
public long Id { get; set; }
public uint Concurrency { get; set; }
[Required]
public Guid IntegrationAppId { get; set; }//Guid of integrating application. All data for it is stored under this guid key. This guid should be fixed and unchanged for all users of the integration app, i.e. it shouldn't be unique to each install but unique to itself once, a publish app id
public Guid IntegrationAppId { get; set; } = Guid.Empty;//Guid of integrating application. All data for it is stored under this guid key. This guid should be fixed and unchanged for all users of the integration app, i.e. it shouldn't be unique to each install but unique to itself once, a publish app id
[Required]
public string Name { get; set; }
public bool Active { get; set; }//integration apps should always check if this is true before working or not and give appropriate error if turned off