using System; 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; using System.ComponentModel.DataAnnotations; namespace AyaNova.Api.Controllers { /// /// License route /// [ApiController] [Asp.Versioning.ApiVersion("8.0")] [Route("api/v{version:apiVersion}/license")] [Produces("application/json")] [Authorize] public class LicenseController : ControllerBase { private readonly AyContext ct; private readonly ILogger log; private readonly ApiServerState serverState; /// /// ctor /// /// /// /// public LicenseController(AyContext dbcontext, ILogger logger, ApiServerState apiServerState) { ct = dbcontext; log = logger; serverState = apiServerState; } /// /// Get License info /// /// Information about the currently installed license in AyaNova [HttpGet()] public ActionResult GetLicenseInfo() { if (serverState.IsClosed) { //Exception for SuperUser account to handle licensing issues if (UserIdFromContext.Id(HttpContext.Items) != 1) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); } if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.License)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } var ret = AyaNova.Core.License.LicenseInfoAsJson; return Ok(ApiOkResponse.Response(ret)); } /// /// Get DB Emptiness /// /// Checks if there is any data in critical tables only [HttpGet("database-empty")] public async Task GetDbEmpty() { if (serverState.IsClosed) { //Exception for SuperUser account to handle licensing issues if (UserIdFromContext.Id(HttpContext.Items) != 1) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); } if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.License)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } return Ok(ApiOkResponse.Response(await AyaNova.Util.DbUtil.DBIsEmptyAsync(ct, log))); } /// /// Fetch license /// /// Posting to this route causes AyaNova to attempt to refresh it's license /// from the AyaNova license server /// /// On success returns information about the currently installed license in AyaNova [HttpPost] public async Task FetchLicense() { if (serverState.IsClosed) { //Exception for SuperUser account to handle licensing issues if (UserIdFromContext.Id(HttpContext.Items) != 1) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); } if (!Authorized.HasCreateRole(HttpContext.Items, AyaType.License)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } if (!ModelState.IsValid) { return BadRequest(new ApiErrorResponse(ModelState)); } try { var ret = await AyaNova.Core.License.FetchKeyAsync(serverState, ct, log, false); //most often the result will be not found but in future might be other results //main thing is to not log anything but OK response if (ret == "ok") { //Eventlog await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.License, AyaEvent.LicenseFetch), ct); } else { if (ret != "notfound") log.LogError($"LicenseController::FetchLicense - failed: {ret}"); } //any response here is OK, there's not necessarily an error, just no key or some mundane problem not technical but biz so let //ret string instruct client on what to do return Ok(ret); } catch (Exception ex) { Exception rootex = ex; while (rootex.InnerException != null) { rootex = rootex.InnerException; } if (rootex.Message.Contains("E1020")) { return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "LICENSE_KEY", rootex.Message)); } else { throw; } } } /// /// Request trial license /// /// Posting to this route causes AyaNova to request a trial license key from the AyaNova license server /// Database must be empty and unlicensed or trial license /// /// /// HTTP 204 No Content result code on success or fail code with explanation [HttpPost("trialRequest")] public async Task RequestTrial([FromBody] RequestTrial trialRequest) { if (serverState.IsClosed) { //Exception for SuperUser account to handle licensing issues if (UserIdFromContext.Id(HttpContext.Items) != 1) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); } if (!Authorized.HasCreateRole(HttpContext.Items, AyaType.License)) { return StatusCode(403, new ApiNotAuthorizedResponse()); } if (!ModelState.IsValid) { return BadRequest(new ApiErrorResponse(ModelState)); } if (!await AyaNova.Util.DbUtil.DBIsEmptyAsync(ct, log)) { return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "Only an empty AyaNova database can request a trial key. Erase the database to proceed with a new trial.")); } if (!AyaNova.Core.License.ActiveKey.IsEmpty && !AyaNova.Core.License.ActiveKey.TrialLicense) { return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, null, "There is an active registered license. Only an unlicensed or trial license database can request a trial key.")); } try { //Send the request to RockFish here (or at least start the job to do it in which case return Accepted instead of no content and update comment above) var ret = await Core.License.RequestTrialAsync(trialRequest, log); if (ret == "ok") { //Log await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.License, AyaEvent.LicenseTrialRequest), ct); } return Ok(ret); } catch (Exception ex) { Exception rootex = ex; while (rootex.InnerException != null) { rootex = rootex.InnerException; } if (rootex.Message.Contains("E1020")) { return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "LICENSE_KEY", rootex.Message)); } else { throw; } } } /// /// Permanently erase most data and all attachments for seeding or migration purposes /// /// This route is used by the v8-migration utility and by the AyaNova UI when seeding a trial database /// /// Items retained are documented here with the exception of Tax codes: /// https://ayanova.com/docs/adm-license/#erase-database /// However not Tax Codes as documented because seeding or migration will create tax codes /// /// (Only *the* SuperUser account can use this route) /// /// Must be "I understand" /// HTTP 204 No Content result code on success or fail code with explanation [HttpPost("permanently-erase-all-data")] public async Task RemoveAllData([FromBody] string acceptCode) { if (serverState.IsClosed) { //Exception for SuperUser account to handle licensing issues if (UserIdFromContext.Id(HttpContext.Items) != 1) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); } if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); long UserId = UserIdFromContext.Id(HttpContext.Items); //SuperUser only and must have accept code if (UserId != 1 || string.IsNullOrWhiteSpace(acceptCode) || acceptCode.ToLowerInvariant() != "i understand") return StatusCode(403, new ApiNotAuthorizedResponse()); //empty the db await AyaNova.Util.DbUtil.EmptyBizDataFromDatabaseForSeedingOrImportingAsync(log); //Log await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, 0, AyaType.Global, AyaEvent.EraseAllData), ct); return NoContent(); } /// /// Permanently erase most data and all attachments /// /// This route is used by the by the AyaNova UI when a user selects to manually erase the database /// or when they are evaluating and request another trial period with a database that already has a trial license in it /// /// Items retained are documented here: /// https://ayanova.com/docs/adm-license/#erase-database /// /// /// (Only *the* SuperUser account can use this route) /// /// Must be "I understand" /// HTTP 204 No Content result code on success or fail code with explanation [HttpPost("permanently-erase-all-data-keep-tax-codes")] public async Task RemoveAllDataKeepTaxCodes([FromBody] string acceptCode) { if (serverState.IsClosed) { //Exception for SuperUser account to handle licensing issues if (UserIdFromContext.Id(HttpContext.Items) != 1) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); } if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); long UserId = UserIdFromContext.Id(HttpContext.Items); //SuperUser only and must have accept code if (UserId != 1 || string.IsNullOrWhiteSpace(acceptCode) || acceptCode.ToLowerInvariant() != "i understand") return StatusCode(403, new ApiNotAuthorizedResponse()); //empty the db await AyaNova.Util.DbUtil.EmptyBizDataFromDatabaseForSeedingOrImportingAsync(log, true); //Log await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, 0, AyaType.Global, AyaEvent.EraseAllData), ct); return NoContent(); } /// /// /// /// /// HTTP 204 No Content result code on success or fail code with explanation [AllowAnonymous] [HttpPost("lc")] [ApiExplorerSettings(IgnoreApi = true)] public async Task lc([FromBody] string acceptCode) { //END USER LICENSE AGREEMENT ROUTE ONLY CALLED FROM WEBAPP AND HIDDEN FROM VIEW AS A ROUTE //SuperUser only and must have accept code if (string.IsNullOrWhiteSpace(acceptCode) || acceptCode.ToLowerInvariant() != "iaccepttheagreement") return StatusCode(403, new ApiNotAuthorizedResponse()); await Core.License.FlagEULA(ct, log); //Log await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, 0, AyaType.Global, AyaEvent.Modified, "End user license agreement consent obtained"), ct); return NoContent(); } //------------------------------------------------------ }//eoc }//eons