Initial commit

This commit is contained in:
Jorge Luis caudillo esparza
2026-07-14 11:15:20 -07:00
commit 6685ef63fb
7326 changed files with 1020873 additions and 0 deletions

105
SIG.Web/AccessDenied.aspx Normal file
View File

@@ -0,0 +1,105 @@
<%@ Page Title="Acceso denegado" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="AccessDenied.aspx.cs" Inherits="SIG.Web.AccessDenied" %>
<asp:Content ID="ContentHead" ContentPlaceHolderID="H" runat="server">
<style type="text/css">
.access-denied-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: calc(100vh - 190px);
padding: 30px 15px;
}
.access-denied-card {
width: 100%;
max-width: 680px;
padding: 42px 38px;
background: #ffffff;
border-top: 4px solid #e7505a;
box-shadow: 0 4px 18px rgba(0, 0, 0, .10);
text-align: center;
}
.access-denied-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 84px;
height: 84px;
margin-bottom: 18px;
border-radius: 50%;
color: #e7505a;
background: #fbeaec;
font-size: 38px;
}
.access-denied-code {
margin: 0 0 4px;
color: #e7505a;
font-size: 18px;
font-weight: 700;
letter-spacing: 2px;
}
.access-denied-title {
margin: 0 0 14px;
color: #34495e;
font-size: 30px;
font-weight: 600;
}
.access-denied-message {
max-width: 520px;
margin: 0 auto 28px;
color: #697882;
font-size: 15px;
line-height: 1.7;
}
.access-denied-actions .btn {
margin: 4px;
}
@media (max-width: 600px) {
.access-denied-card {
padding: 32px 20px;
}
.access-denied-title {
font-size: 25px;
}
}
</style>
</asp:Content>
<asp:Content ID="ContentMain" ContentPlaceHolderID="C" runat="server">
<div class="access-denied-wrapper">
<section class="access-denied-card" aria-labelledby="accessDeniedTitle">
<div class="access-denied-icon" aria-hidden="true">
<i class="fa fa-lock"></i>
</div>
<p class="access-denied-code">ERROR 403</p>
<h1 id="accessDeniedTitle" class="access-denied-title">Acceso denegado</h1>
<p class="access-denied-message">
Tu sesión está activa, pero tu usuario no cuenta con permiso para consultar esta página.
Si necesitas acceso, solicita la autorización correspondiente al administrador del sistema.
</p>
<div class="access-denied-actions">
<a class="btn blue" href="<%= ResolveUrl("~/Inicio.aspx") %>">
<i class="fa fa-home"></i> Ir al inicio
</a>
<button type="button" class="btn default" onclick="window.history.back();">
<i class="fa fa-arrow-left"></i> Regresar
</button>
<a class="btn red" href="<%= ResolveUrl("~/Logout.aspx") %>">
<i class="fa fa-sign-out"></i> Cerrar sesión
</a>
</div>
</section>
</div>
</asp:Content>
<asp:Content ID="ContentScripts" ContentPlaceHolderID="J" runat="server">
</asp:Content>

View File

@@ -0,0 +1,14 @@
using System;
using System.Web.UI;
namespace SIG.Web
{
public partial class AccessDenied : Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.StatusCode = 403;
Response.TrySkipIisCustomErrors = true;
}
}
}

6
SIG.Web/AccessDenied.aspx.designer.cs generated Normal file
View File

@@ -0,0 +1,6 @@
namespace SIG.Web
{
public partial class AccessDenied
{
}
}

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<ajaxControlToolkit>
<controlBundles>
<controlBundle name="Accordion">
<control name="AccordionExtender"></control>
<control name="CollapsiblePanelExtender"></control>
</controlBundle>
<!--<controlBundle name="CustomControls">
<control name="AjaxControlToolkitWelcomeLabel.ActWelcomeLabel" assembly="CustomAjaxServerControlSample"></control>
<control name="CollapsiblePanelExtender"></control>
</controlBundle>-->
<controlBundle>
<control name="AjaxFileUpload"></control>
<control name="AlwaysVisibleControlExtender"></control>
<control name="AreaChart"></control>
<control name="AsyncFileUpload"></control>
<control name="AutoCompleteExtender"></control>
<control name="BalloonPopupExtender"></control>
<control name="BarChart"></control>
<control name="BubbleChart"></control>
<control name="CalendarExtender"></control>
<control name="CascadingDropDown"></control>
<control name="CollapsiblePanelExtender"></control>
<control name="ColorPickerExtender"></control>
<control name="ComboBox"></control>
<control name="ConfirmButtonExtender"></control>
<control name="DragPanelExtender"></control>
<control name="DropDownExtender"></control>
<control name="DropShadowExtender"></control>
<control name="DynamicPopulateExtender"></control>
<control name="FilteredTextBoxExtender"></control>
<control name="Gravatar"></control>
<control name="HoverMenuExtender"></control>
<control name="HtmlEditor.Editor"></control>
<control name="HtmlEditorExtender"></control>
<control name="LineChart"></control>
<control name="ListSearchExtender"></control>
<control name="MaskedEditExtender"></control>
<control name="ModalPopupExtender"></control>
<control name="MultiHandleSliderExtender"></control>
<control name="MutuallyExclusiveCheckBoxExtender"></control>
<control name="NoBotExtender"></control>
<control name="NumericUpDownExtender"></control>
<control name="PagingBulletedListExtender"></control>
<control name="PasswordStrength"></control>
<control name="PieChart"></control>
<control name="PopupControlExtender"></control>
<control name="RatingExtender"></control>
<control name="ReorderList"></control>
<control name="ResizableControlExtender"></control>
<control name="RoundedCornersExtender"></control>
<control name="Seadragon"></control>
<control name="SliderExtender"></control>
<control name="SlideShowExtender"></control>
<control name="TabContainer"></control>
<control name="TextBoxWatermarkExtender"></control>
<control name="ToggleButtonExtender"></control>
<control name="Twitter"></control>
<control name="UpdatePanelAnimationExtender"></control>
<control name="ValidatorCalloutExtender"></control>
</controlBundle>
</controlBundles>
</ajaxControlToolkit>

View File

@@ -0,0 +1,59 @@
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Search;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Notifications.Contracts;
using SIG.Web.AppCode.Notifications.Services;
using SIG.Web.AppCode.Repositories.Security;
using SIG.Web.AppCode.Services.Database;
using SIG.Web.AppCode.Services.Menu;
using SIG.Web.AppCode.Services.Search;
using SIG.Web.AppCode.Services.Security;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Configuration
{
public static class AppServices
{
public static IDbConnectionFactory DbConnectionFactory { get; private set; }
public static IDbExecutor DbExecutor { get; private set; }
public static IPasswordHasher PasswordHasher { get; private set; }
public static IUserRepository UserRepository { get; private set; }
public static IUserAdministrationRepository UserAdministrationRepository { get; private set; }
public static IPublicRegistrationService PublicRegistrationService { get; private set; }
public static ILoginAttemptRepository LoginAttemptRepository { get; private set; } public static IUserSessionRepository UserSessionRepository { get; private set; }
public static ILoginService LoginService { get; private set; }
public static IUserContextProvider UserContext { get; private set; }
public static IPermissionService PermissionService { get; private set; }
public static IMenuProvider MenuProvider { get; private set; }
public static INotificationCenter NotificationCenter { get; private set; }
public static IBuscadorCatalogoDataProvider SearchProvider { get; private set; }
public static void Configure()
{
DbConnectionFactory = new DbConnectionFactory();
DbExecutor = new DbExecutor(DbConnectionFactory);
PasswordHasher = new PasswordHasher();
UserRepository = new UserRepository(DbExecutor);
UserAdministrationRepository = new UserAdministrationRepository(DbExecutor);
LoginAttemptRepository = new LoginAttemptRepository(DbExecutor); UserSessionRepository = new UserSessionRepository(DbExecutor);
UserContext = new WebFormsUserContextProvider();
PermissionService = new PermissionService(DbExecutor);
MenuProvider = new MenuProvider(DbExecutor);
NotificationCenter = new DummyNotificationCenter();
SearchProvider = new DummyBuscadorCatalogoDataProvider();
LoginService = new LoginService(
UserRepository,
PasswordHasher,
LoginAttemptRepository,
UserSessionRepository);
PublicRegistrationService = new PublicRegistrationService(
UserAdministrationRepository,
PasswordHasher,
new SmtpEmailSender());
} }
}

View File

@@ -0,0 +1,127 @@
using System.Web.Optimization;
using System.Web.UI;
namespace SIG.Web.AppCode.Configuration
{
public static class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Metronic/assets/global/plugins/jquery.min.js"
));
ScriptManager.ScriptResourceMapping.AddDefinition("jquery", null, new ScriptResourceDefinition
{
Path = "~/bundles/jquery"
});
bundles.Add(new ScriptBundle("~/bundles/scripts").Include(
"~/Metronic/assets/global/plugins/jquery-ui/jquery-ui.min.js",
"~/Metronic/assets/global/plugins/jquery-migrate.min.js",
"~/Scripts/funciones.js",
"~/Scripts/FixFocus.js",
"~/Scripts/jquery.number.js",
"~/Scripts/jquery.maskMoney.js",
"~/Scripts/jquery.thickbox.js",
"~/Scripts/jquery.fancybox-1.3.4.js",
"~/Scripts/jquery.maskedinput-1.2.2.min.js",
"~/Scripts/raphael.2.1.0.min.js",
"~/Scripts/justgage.1.0.1.min.js",
"~/Scripts/multiselect.js",
"~/Scripts/jquery.mask.min.js",
"~/Scripts/gridviewScroll.js",
"~/Metronic/Carusel/slick.min.js"
));
bundles.Add(new ScriptBundle("~/Metronic/assets/global/scripts/JSV1").Include(
"~/Metronic/assets/global/plugins/bootstrap/js/bootstrap.min.js",
"~/Metronic/assets/global/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.min.js",
"~/Metronic/assets/global/plugins/jquery-slimscroll/jquery.slimscroll.min.js",
"~/Metronic/assets/global/plugins/jquery.blockui.min.js",
"~/Metronic/assets/global/plugins/jquery.cokie.min.js",
"~/Metronic/assets/global/plugins/bootstrap-switch/js/bootstrap-switch.min.js",
"~/Metronic/assets/global/plugins/select2/select2.full.min.js",
"~/Metronic/assets/global/plugins/select2/i18n/es.js",
"~/Metronic/assets/global/plugins/bootstrap-confirmation/bootstrap-confirmation.min.js",
"~/Metronic/assets/global/plugins/bootbox/bootbox.min.js",
"~/Metronic/assets/global/plugins/bootstrap-toastr/toastr.min.js",
"~/Metronic/assets/global/scripts/metronic.js",
"~/Metronic/assets/admin/layout/scripts/layout.js",
"~/Metronic/assets/admin/layout/scripts/quick-sidebar.js",
"~/Metronic/assets/admin/layout/scripts/demo.js",
"~/Scripts/porcentaje-box.js",
"~/Scripts/DropdownGrid.js",
"~/Scripts/UcBuscadorCatalogo.js",
"~/Scripts/SigPdfViewer.js",
"~/Scripts/SigPdfDrawer.js",
"~/Scripts/custom.js"
));
bundles.Add(new ScriptBundle("~/bundles/WebFormsJs").Include(
"~/Scripts/WebForms/WebForms.js",
"~/Scripts/WebForms/WebUIValidation.js",
"~/Scripts/WebForms/MenuStandards.js",
"~/Scripts/WebForms/Focus.js",
"~/Scripts/WebForms/GridView.js",
"~/Scripts/WebForms/DetailsView.js",
"~/Scripts/WebForms/TreeView.js",
"~/Scripts/WebForms/WebParts.js"
));
ScriptManager.ScriptResourceMapping.AddDefinition("WebFormsBundle", new ScriptResourceDefinition
{
Path = "~/bundles/WebFormsJs"
});
bundles.Add(new ScriptBundle("~/bundles/MsAjaxJs").Include(
"~/Scripts/WebForms/MsAjax/MicrosoftAjax.js",
"~/Scripts/WebForms/MsAjax/MicrosoftAjaxApplicationServices.js",
"~/Scripts/WebForms/MsAjax/MicrosoftAjaxTimer.js",
"~/Scripts/WebForms/MsAjax/MicrosoftAjaxWebForms.js"
));
ScriptManager.ScriptResourceMapping.AddDefinition("MsAjaxBundle", new ScriptResourceDefinition
{
Path = "~/bundles/MsAjaxJs"
});
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include("~/Scripts/modernizr-*"));
ScriptManager.ScriptResourceMapping.AddDefinition("respond", new ScriptResourceDefinition
{
Path = "~/Scripts/respond.min.js",
DebugPath = "~/Scripts/respond.js"
});
bundles.Add(new StyleBundle("~/Content/AjaxControlToolkit/Styles/Bundle").Include(
"~/Content/AjaxControlToolkit/Styles/AjaxFileUpload.min.css",
"~/Content/AjaxControlToolkit/Styles/Calendar.min.css",
"~/Content/AjaxControlToolkit/Styles/Tabs.min.css",
"~/Content/AjaxControlToolkit/Styles/ValidatorCallout.min.css",
"~/Content/AjaxControlToolkit/Styles/HtmlEditorExtender.min.css"
));
bundles.Add(new StyleBundle("~/Metronic/assets/global/css/cssV1")
.Include("~/Metronic/assets/global/plugins/font-awesome/css/font-awesome.min.css", new CssRewriteUrlTransform())
.Include("~/Metronic/assets/global/plugins/bootstrap/css/bootstrap.min.css", new CssRewriteUrlTransform())
.Include("~/Metronic/assets/global/plugins/simple-line-icons/simple-line-icons.min.css", new CssRewriteUrlTransform())
.Include(
"~/Styles/porcentaje-box.css",
"~/Styles/DropdownGrid.css",
"~/Styles/UcBuscadorCatalogo.css",
"~/Styles/SigPdfViewer.css",
"~/Styles/SigPdfDrawer.css",
"~/Styles/Estilos2.css",
"~/Metronic/assets/global/plugins/bootstrap-switch/css/bootstrap-switch.min.css",
"~/Metronic/assets/global/plugins/bootstrap-toastr/toastr.min.css",
"~/Metronic/assets/global/plugins/select2/select2.css",
"~/Metronic/assets/global/css/components.css",
"~/Metronic/assets/global/css/plugins.css",
"~/Metronic/assets/admin/layout/css/layout.css",
"~/Metronic/assets/admin/layout/css/custom.css")
.Include("~/Metronic/Carusel/slick.css", new CssRewriteUrlTransform())
.Include("~/Metronic/Carusel/slick-theme.css", new CssRewriteUrlTransform()));
}
}
}

View File

@@ -0,0 +1,294 @@
using System;
using System.Data.SqlClient;
using System.Text;
using System.Web;
using SIG.Web.AppCode.Services.Errors;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Configuration
{
public static class ErrorHandlerConfig
{
public static void Register()
{
}
public static void HandleServerError(
Exception exception,
HttpContext context)
{
if (exception == null || context == null)
{
return;
}
try
{
UserSessionModel usuario = ObtenerUsuarioActual();
string cveUsuario = usuario != null
? usuario.CveUsuario.ToString()
: string.Empty;
string nombreUsuario = usuario != null
? usuario.Nombre
: string.Empty;
int cveRole = usuario != null
? usuario.CveRol
: 0;
string codigo = SIGErrorManager.Registrar(
exception,
context,
"Application_Error",
cveUsuario,
nombreUsuario
);
bool esPeticionAsincrona =
SIGErrorManager.EsPeticionAsincrona(context);
/*
* El detalle t<>cnico completo siempre se guarda en log.
* Solo el administrador puede verlo en la respuesta.
*/
bool mostrarDetalle = cveRole == 1;
context.Server.ClearError();
if (esPeticionAsincrona)
{
EscribirErrorAsincrono(
context,
codigo,
exception,
mostrarDetalle
);
return;
}
EscribirPaginaError(
context,
codigo,
exception,
mostrarDetalle,
cveUsuario,
nombreUsuario,
cveRole
);
}
catch (Exception handlerException)
{
try
{
SIGErrorManager.Registrar(
handlerException,
context,
"ErrorHandlerConfig.HandleServerError",
string.Empty,
string.Empty
);
}
catch
{
// El manejador global nunca debe provocar otro error.
}
try
{
context.Server.ClearError();
}
catch
{
}
}
}
public static string GetFriendlyMessage(
Exception exception)
{
if (exception == null)
{
return "Ocurri<72> un error inesperado.";
}
Exception baseException =
exception.GetBaseException();
SqlException sqlException =
baseException as SqlException;
if (sqlException != null)
{
switch (sqlException.Number)
{
case 207:
return "No fue posible completar la operaci<63>n porque falta una columna requerida en la base de datos.";
case 208:
return "No fue posible completar la operaci<63>n porque falta una tabla o vista requerida en la base de datos.";
case 4060:
return "No fue posible abrir la base de datos configurada.";
case 18456:
return "No fue posible iniciar sesi<73>n en la base de datos.";
case -2:
return "La base de datos tard<72> demasiado en responder.";
case 1205:
return "La operaci<63>n no pudo completarse porque ocurri<72> un bloqueo en la base de datos. Intente nuevamente.";
case 547:
return "No fue posible completar la operaci<63>n porque existen registros relacionados.";
case 2627:
case 2601:
return "Ya existe un registro con la misma informaci<63>n.";
default:
return "Ocurri<72> un error al consultar la base de datos.";
}
}
if (baseException is TimeoutException)
{
return "La solicitud tard<72> demasiado en responder. Intente nuevamente.";
}
if (baseException is OperationCanceledException)
{
return "La operaci<63>n fue cancelada antes de completarse.";
}
HttpException httpException =
baseException as HttpException;
if (httpException != null)
{
int statusCode =
httpException.GetHttpCode();
switch (statusCode)
{
case 400:
return "La solicitud enviada no es v<>lida.";
case 401:
return "Su sesi<73>n no tiene autorizaci<63>n para realizar esta operaci<63>n.";
case 403:
return "No tiene permisos para realizar esta operaci<63>n.";
case 404:
return "No se encontr<74> el recurso solicitado.";
case 408:
return "La solicitud tard<72> demasiado en responder.";
case 413:
return "El archivo o contenido enviado supera el tama<6D>o permitido.";
default:
return "Ocurri<72> un error al procesar la solicitud.";
}
}
return "Ocurri<72> un error inesperado. Intente nuevamente.";
}
private static void EscribirErrorAsincrono(
HttpContext context,
string codigo,
Exception exception,
bool mostrarDetalle)
{
string respuesta =
"SIG_ERROR|" + codigo;
if (mostrarDetalle)
{
string detalle =
SIGErrorManager.ObtenerDetalleTecnico(
exception
);
string detalleBase64 =
Convert.ToBase64String(
Encoding.UTF8.GetBytes(detalle)
);
respuesta += "|" + detalleBase64;
}
context.Response.Clear();
context.Response.ClearHeaders();
context.Response.StatusCode = 500;
context.Response.TrySkipIisCustomErrors = true;
context.Response.ContentType =
"text/plain; charset=utf-8";
context.Response.AddHeader(
"X-SIG-Error-Code",
codigo
);
context.Response.Write(respuesta);
context.ApplicationInstance.CompleteRequest();
}
private static void EscribirPaginaError(
HttpContext context,
string codigo,
Exception exception,
bool mostrarDetalle,
string cveUsuario,
string nombreUsuario,
int cveRole)
{
string html =
SIGErrorManager.CrearHtmlError(
codigo,
exception,
mostrarDetalle,
cveUsuario,
nombreUsuario,
cveRole
);
context.Response.Clear();
context.Response.ClearHeaders();
context.Response.StatusCode = 500;
context.Response.TrySkipIisCustomErrors = true;
context.Response.ContentType =
"text/html; charset=utf-8";
context.Response.Write(html);
context.ApplicationInstance.CompleteRequest();
}
private static UserSessionModel ObtenerUsuarioActual()
{
try
{
if (AppServices.UserContext == null)
{
return null;
}
return AppServices.UserContext.GetCurrentUser();
}
catch
{
return null;
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace SIG.Web.AppCode.Contracts.Data
{
public enum DbProviderKind
{
SqlServer,
PostgreSql
}
}

View File

@@ -0,0 +1,10 @@
namespace SIG.Web.AppCode.Contracts.Data
{
public sealed class DomainConnectionSettings
{
public string Domain { get; set; }
public DbProviderKind Provider { get; set; }
public string ConnectionStringName { get; set; }
public bool IsDefault { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
using System.Data;
namespace SIG.Web.AppCode.Contracts.Data
{
public interface IDbConnectionFactory
{
IDbConnection Create(string domain);
}
}

View File

@@ -0,0 +1,7 @@
namespace SIG.Web.AppCode.Contracts.Data
{
public interface IDomainConnectionResolver
{
DomainConnectionSettings Resolve(string domain);
}
}

View File

@@ -0,0 +1,5 @@
using System.Data;
namespace SIG.Web.AppCode.Contracts.Database
{
public interface IDbConnectionFactory { IDbConnection CreateConnection(string domainName); }
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Data;
using SIG.Web.AppCode.Models.Common;
namespace SIG.Web.AppCode.Contracts.Database
{
public interface IDbExecutor
{
DbResult<T> Execute<T>(string domainName, Func<IDbConnection, T> action);
}
}

View File

@@ -0,0 +1,7 @@
namespace SIG.Web.AppCode.Contracts.Login
{
public interface ILoginService
{
LoginResult Login(string userName, string password, string domain);
}
}

View File

@@ -0,0 +1,21 @@
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Contracts.Login
{
public sealed class LoginResult
{
public bool Exitoso { get; private set; }
public string Mensaje { get; private set; }
public UserSessionModel Usuario { get; private set; }
public static LoginResult Ok(UserSessionModel usuario)
{
return new LoginResult { Exitoso = true, Usuario = usuario, Mensaje = "OK" };
}
public static LoginResult Fail(string mensaje)
{
return new LoginResult { Exitoso = false, Mensaje = mensaje };
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
namespace SIG.Web.AppCode.Contracts.Search
{
public sealed class BuscadorCatalogoFiltro
{
public string Campo { get; set; }
public string Operador { get; set; }
public string Valor { get; set; }
}
public sealed class BuscadorCatalogoOrden
{
public string Campo { get; set; }
public string Direccion { get; set; } = "ASC";
}
public sealed class BuscadorCatalogoColumna
{
public string Campo { get; set; }
public string Titulo { get; set; }
public bool Visible { get; set; } = true;
public bool Busqueda { get; set; } = true;
public string CssClass { get; set; }
}
public sealed class BuscadorCatalogoCallbackSolicitud
{
public string Catalogo { get; set; }
public string Termino { get; set; }
public int Pagina { get; set; } = 1;
public int TamanoPagina { get; set; } = 20;
public List<BuscadorCatalogoFiltro> Filtros { get; set; } = new List<BuscadorCatalogoFiltro>();
public List<BuscadorCatalogoOrden> Orden { get; set; } = new List<BuscadorCatalogoOrden>();
}
public sealed class BuscadorCatalogoCallbackRespuesta
{
public bool Success { get; set; } = true;
public string Message { get; set; }
public int Total { get; set; }
public List<Dictionary<string, object>> Rows { get; set; } = new List<Dictionary<string, object>>();
}
}

View File

@@ -0,0 +1,9 @@
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Contracts.Search
{
public interface IBuscadorCatalogoDataProvider
{
BuscadorCatalogoCallbackRespuesta Search(BuscadorCatalogoCallbackSolicitud request, UserSessionModel user);
}
}

View File

@@ -0,0 +1,8 @@
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IEmailSender
{
bool IsConfigured { get; }
void SendVerification(string recipient, string verificationUrl);
}
}

View File

@@ -0,0 +1,9 @@
using SIG.Web.AppCode.Models.Common;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface ILoginAttemptRepository
{
DbResult<bool> Save(int? cveUser, string username, bool wasSuccessful, string failureReason, string ipAddress, string userAgent, string domainName);
}
}

View File

@@ -0,0 +1,9 @@
using SIG.Web.AppCode.Models.Security;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface ILoginService
{
LoginResult Login(LoginRequest request);
void Logout(string sessionToken, string domainName);
}
}

View File

@@ -0,0 +1,8 @@
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IPasswordHasher
{
string HashPassword(string password);
bool VerifyPassword(string password, string storedHash);
}
}

View File

@@ -0,0 +1,8 @@
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IPermissionService
{
bool HasPermission(int cveUser, string permissionCode);
bool CanAccessPage(int cveUser, string pageUrl);
}
}

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Security;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IPublicRegistrationService
{
DbResult<bool> Validate(PublicRegistrationDraft draft, string domainName);
DbResult<List<StateOptionModel>> ListStates(string domainName);
DbResult<bool> Register(PublicRegistrationDraft draft, string verificationBaseUrl, string domainName);
DbResult<bool> Verify(string token, string domainName);
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Security;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IUserAdministrationRepository
{
DbResult<List<UserAdministrationModel>> ListUsers(string text, int executingUser, string domainName);
DbResult<UserAdministrationModel> GetUser(int userId, int executingUser, string domainName);
DbResult<List<RoleOptionModel>> ListRoles(int executingUser, string domainName);
DbResult<List<StateOptionModel>> ListStates(string domainName);
DbResult<int> SaveUser(UserAdministrationModel user, string passwordHash, int executingUser, string domainName);
DbResult<bool> DeleteUser(int userId, int executingUser, string domainName);
DbResult<List<UserClientModel>> ListUserClients(int userId, int executingUser, string domainName);
DbResult<bool> SaveUserClient(int userId, int clientId, int order, bool primary, int executingUser, string domainName);
DbResult<bool> ValidateRegistration(string username, string email, string rfc, string domainName);
DbResult<RegistrationCreatedModel> CreateRegistration(PublicRegistrationDraft draft, string passwordHash, string tokenHash, string domainName);
DbResult<bool> VerifyEmail(string tokenHash, string domainName);
}
}

View File

@@ -0,0 +1,8 @@
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IUserContextProvider
{
bool IsAuthenticated(); UserSessionModel GetCurrentUser(); void SetCurrentUser(UserSessionModel user); void Clear();
}
}

View File

@@ -0,0 +1,12 @@
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Security;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IUserRepository
{
DbResult<UserModel> ObtenerPorNombreUsuario(string username, string domainName);
DbResult<bool> RegisterSuccessfulLogin(int cveUser, string domainName);
DbResult<bool> RegisterFailedLogin(int cveUser, string domainName);
}
}

View File

@@ -0,0 +1,10 @@
using SIG.Web.AppCode.Models.Common;
namespace SIG.Web.AppCode.Contracts.Security
{
public interface IUserSessionRepository
{
DbResult<bool> StartSession(int cveUser, string sessionToken, string ipAddress, string userAgent, string domainName);
DbResult<bool> EndSession(string sessionToken, string domainName);
}
}

View File

@@ -0,0 +1,37 @@
using System;
namespace SIG.Web.AppCode.Models.Common
{
public sealed class DbResult<T>
{
public bool Success { get; private set; }
public T Data { get; private set; }
public string UserMessage { get; private set; }
public string TechnicalMessage { get; private set; }
public Exception Exception { get; private set; }
public static DbResult<T> Ok(T data)
{
return new DbResult<T>
{
Success = true,
Data = data,
UserMessage = string.Empty,
TechnicalMessage = string.Empty,
Exception = null
};
}
public static DbResult<T> Fail(string userMessage, string technicalMessage, Exception exception)
{
return new DbResult<T>
{
Success = false,
Data = default(T),
UserMessage = userMessage,
TechnicalMessage = technicalMessage,
Exception = exception
};
}
}
}

View File

@@ -0,0 +1,9 @@
namespace SIG.Web.AppCode.Models.Common
{
public sealed class StoredProcedureResult
{
public bool Return { get; set; }
public int ErrorTipo { get; set; }
public string ErrorMostrar { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace SIG.Web.AppCode.Models.Security
{
public sealed class LoginRequest
{
public string NombreUsuario { get; set; }
public string Contrasena { get; set; }
public string NombreDominio { get; set; }
public string DireccionIP { get; set; }
public string AgenteUsuario { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Models.Security
{
public sealed class LoginResult
{
public bool Exitoso { get; private set; }
public string Mensaje { get; private set; }
public UserSessionModel Usuario { get; private set; }
public static LoginResult Ok(UserSessionModel usuario) { return new LoginResult { Exitoso = true, Usuario = usuario, Mensaje = string.Empty }; }
public static LoginResult Fail(string mensaje) { return new LoginResult { Exitoso = false, Mensaje = mensaje }; }
}
}

View File

@@ -0,0 +1,70 @@
using System;
namespace SIG.Web.AppCode.Models.Security
{
[Serializable]
public sealed class PublicRegistrationDraft
{
public string NombreUsuario { get; set; }
public string CorreoElectronico { get; set; }
public string Contrasena { get; set; }
public string RFC { get; set; }
public string RazonSocial { get; set; }
public string NombreComercial { get; set; }
public string Direccion { get; set; }
public string NoExt { get; set; }
public string NoInt { get; set; }
public string Colonia { get; set; }
public string Ciudad { get; set; }
public int CveSATEstado { get; set; }
public string Pais { get; set; }
public string CP { get; set; }
public string Telefono { get; set; }
}
public sealed class UserAdministrationModel
{
public int CveUsuario { get; set; }
public string NombreUsuario { get; set; }
public string CorreoElectronico { get; set; }
public int CveRol { get; set; }
public string NombreRol { get; set; }
public bool Activo { get; set; }
public bool DebeCambiarContrasena { get; set; }
public bool CorreoVerificado { get; set; }
public DateTime FechaCreacion { get; set; }
}
public sealed class RoleOptionModel
{
public int CveRol { get; set; }
public string NombreRol { get; set; }
public string Descripcion { get; set; }
}
public sealed class StateOptionModel
{
public int CveEstado { get; set; }
public string ClaveEstado { get; set; }
public string NombreEstado { get; set; }
}
public sealed class UserClientModel
{
public int CveUsuarioCliente { get; set; }
public int CveUsuario { get; set; }
public int CveCliente { get; set; }
public string Nombre { get; set; }
public string RFC { get; set; }
public int Orden { get; set; }
public bool EsPrincipal { get; set; }
public bool Activo { get; set; }
public bool ClienteActivo { get; set; }
}
public sealed class RegistrationCreatedModel
{
public int CveUsuario { get; set; }
public int CveCliente { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace SIG.Web.AppCode.Models.Security
{
public sealed class UserModel
{
public int CveUsuario { get; set; }
public string NombreUsuario { get; set; }
public string HashContrasena { get; set; }
public string CorreoElectronico { get; set; }
public int CveRol { get; set; }
public string NombreRol { get; set; }
public bool Activo { get; set; }
public bool CorreoVerificado { get; set; }
public bool DebeCambiarContrasena { get; set; } public int IntentosFallidosInicioSesion { get; set; }
public DateTime? BloqueadoHasta { get; set; }
public DateTime? FechaUltimoInicioSesion { get; set; }
public DateTime? FechaUltimaConexion { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using SIG.Web.AppCode.Notifications.Models;
namespace SIG.Web.AppCode.Notifications.Contracts
{
public interface INotificationCenter
{
void Queue(NotificationMessage message);
void MarkAsShown(int notificationId, int userId);
IReadOnlyList<NotificationMessage> GetBellMessages(int userId);
int CountPending(int userId);
}
}

View File

@@ -0,0 +1,12 @@
using Microsoft.AspNet.SignalR;
namespace SIG.Web.AppCode.Notifications.Hubs
{
public class NotificationHub : Hub
{
public void Ping()
{
Clients.Caller.pong("SignalR is working.");
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
namespace SIG.Web.AppCode.Notifications.Models
{
[Serializable]
public sealed class NotificationMessage
{
public int CveNotificacion { get; set; }
public int CveUsuario { get; set; }
public string TipoNotificacion { get; set; }
public string EstadoNotificacion { get; set; }
public string Titulo { get; set; }
public string Mensaje { get; set; }
public string Url { get; set; }
public bool Leida { get; set; }
public bool MostradaEnCampana { get; set; }
public DateTime? FechaEnvio { get; set; }
public DateTime? FechaLectura { get; set; }
public DateTime FechaCreacion { get; set; }
public DateTime? FechaActualizacion { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using SIG.Web.AppCode.Notifications.Contracts;
using SIG.Web.AppCode.Notifications.Models;
namespace SIG.Web.AppCode.Notifications.Services
{
public sealed class DummyNotificationCenter : INotificationCenter
{
private static readonly List<NotificationMessage> Items = new List<NotificationMessage>();
public void Queue(NotificationMessage message)
{
if (message == null) return;
message.CveNotificacion = Items.Count + 1;
message.FechaCreacion = System.DateTime.Now;
message.EstadoNotificacion = "Pendiente";
Items.Add(message);
}
public void MarkAsShown(int notificationId, int userId)
{
var item = Items.FirstOrDefault(x => x.CveNotificacion == notificationId && x.CveUsuario == userId);
if (item == null) return;
item.MostradaEnCampana = true;
}
public IReadOnlyList<NotificationMessage> GetBellMessages(int userId)
{
return Items.Where(x => x.CveUsuario == userId && !x.Leida).ToList();
}
public int CountPending(int userId)
{
return GetBellMessages(userId).Count;
}
}
}

View File

@@ -0,0 +1,40 @@
using System.Data;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Services.Database;
namespace SIG.Web.AppCode.Repositories.Security
{
public sealed class LoginAttemptRepository : ILoginAttemptRepository
{
private readonly IDbExecutor _dbExecutor;
public LoginAttemptRepository(IDbExecutor dbExecutor)
{
_dbExecutor = dbExecutor;
}
public DbResult<bool> Save(int? cveUser, string username, bool wasSuccessful, string failureReason, string ipAddress, string userAgent, string domainName)
{
return _dbExecutor.Execute(domainName, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_IntentoInicioSesionAdd", cveUser ?? 0);
StoredProcedureCommand.AddInput(command, "@CveUsuario", cveUser.HasValue ? (object)cveUser.Value : null, DbType.Int32);
StoredProcedureCommand.AddInput(command, "@NombreUsuario", username, DbType.String);
StoredProcedureCommand.AddInput(command, "@Exitoso", wasSuccessful, DbType.Boolean);
StoredProcedureCommand.AddInput(command, "@MotivoFallo", failureReason, DbType.String);
StoredProcedureCommand.AddInput(command, "@DireccionIP", ipAddress, DbType.String);
StoredProcedureCommand.AddInput(command, "@AgenteUsuario", userAgent, DbType.String);
StoredProcedureCommand.AddInput(command, "@NombreDominio", domainName, DbType.String);
command.ExecuteNonQuery();
StoredProcedureCommand.EnsureSuccess(command);
return true;
}
});
}
}
}

View File

@@ -0,0 +1,143 @@
using System.Collections.Generic;
using System.Data;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Security;
using SIG.Web.AppCode.Services.Database;
using SIG.Web.AppCode.Utilities;
namespace SIG.Web.AppCode.Repositories.Security
{
public sealed class UserAdministrationRepository : IUserAdministrationRepository
{
private readonly IDbExecutor _db;
public UserAdministrationRepository(IDbExecutor db) { _db = db; }
public DbResult<List<UserAdministrationModel>> ListUsers(string text, int executingUser, string domainName)
{ return QueryList<UserAdministrationModel>("dbo.PA_Sis_UsuarioList", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@Texto", text, DbType.String)); }
public DbResult<UserAdministrationModel> GetUser(int userId, int executingUser, string domainName)
{ return QueryOne<UserAdministrationModel>("dbo.PA_Sis_UsuarioGet", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32)); }
public DbResult<List<RoleOptionModel>> ListRoles(int executingUser, string domainName)
{ return QueryList<RoleOptionModel>("dbo.PA_Sis_RolList", executingUser, domainName, null); }
public DbResult<List<StateOptionModel>> ListStates(string domainName)
{ return QueryList<StateOptionModel>("dbo.PA_Gral_EstadoList", 0, domainName, null); }
public DbResult<int> SaveUser(UserAdministrationModel user, string passwordHash, int executingUser, string domainName)
{
return _db.Execute(domainName, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioSave", executingUser);
StoredProcedureCommand.AddInput(command, "@CveUsuario", user.CveUsuario <= 0 ? (object)null : user.CveUsuario, DbType.Int32);
StoredProcedureCommand.AddInput(command, "@NombreUsuario", user.NombreUsuario, DbType.String);
StoredProcedureCommand.AddInput(command, "@CorreoElectronico", user.CorreoElectronico, DbType.String);
StoredProcedureCommand.AddInput(command, "@CveRol", user.CveRol, DbType.Int32);
StoredProcedureCommand.AddInput(command, "@HashContrasena", passwordHash, DbType.String);
StoredProcedureCommand.AddInput(command, "@Activo", user.Activo, DbType.Boolean);
StoredProcedureCommand.AddInput(command, "@DebeCambiarContrasena", user.DebeCambiarContrasena, DbType.Boolean);
int id;
using (IDataReader reader = command.ExecuteReader()) { id = reader.Read() ? reader.GetInt32(0) : 0; }
StoredProcedureCommand.EnsureSuccess(command);
return id;
}
});
}
public DbResult<bool> DeleteUser(int userId, int executingUser, string domainName)
{ return Execute("dbo.PA_Sis_UsuarioDelete", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32)); }
public DbResult<List<UserClientModel>> ListUserClients(int userId, int executingUser, string domainName)
{ return QueryList<UserClientModel>("dbo.PA_Sis_UsuarioClienteList", executingUser, domainName, c => StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32)); }
public DbResult<bool> SaveUserClient(int userId, int clientId, int order, bool primary, int executingUser, string domainName)
{
return Execute("dbo.PA_Sis_UsuarioClienteSave", executingUser, domainName, c =>
{
StoredProcedureCommand.AddInput(c, "@CveUsuario", userId, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@CveCliente", clientId, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@Orden", order, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@EsPrincipal", primary, DbType.Boolean);
});
}
public DbResult<bool> ValidateRegistration(string username, string email, string rfc, string domainName)
{
return Execute("dbo.PA_Sis_RegistroClienteValidate", 0, domainName, c =>
{
StoredProcedureCommand.AddInput(c, "@NombreUsuario", username, DbType.String);
StoredProcedureCommand.AddInput(c, "@CorreoElectronico", email, DbType.String);
StoredProcedureCommand.AddInput(c, "@RFC", rfc, DbType.String);
});
}
public DbResult<RegistrationCreatedModel> CreateRegistration(PublicRegistrationDraft draft, string passwordHash, string tokenHash, string domainName)
{
return QueryOne<RegistrationCreatedModel>("dbo.PA_Sis_RegistroClienteAdd", 0, domainName, c =>
{
StoredProcedureCommand.AddInput(c, "@NombreUsuario", draft.NombreUsuario, DbType.String);
StoredProcedureCommand.AddInput(c, "@HashContrasena", passwordHash, DbType.String);
StoredProcedureCommand.AddInput(c, "@CorreoElectronico", draft.CorreoElectronico, DbType.String);
StoredProcedureCommand.AddInput(c, "@RFC", draft.RFC, DbType.String);
StoredProcedureCommand.AddInput(c, "@RazonSocial", draft.RazonSocial, DbType.String);
StoredProcedureCommand.AddInput(c, "@NombreComercial", draft.NombreComercial, DbType.String);
StoredProcedureCommand.AddInput(c, "@Direccion", draft.Direccion, DbType.String);
StoredProcedureCommand.AddInput(c, "@NoExt", draft.NoExt, DbType.String);
StoredProcedureCommand.AddInput(c, "@NoInt", draft.NoInt, DbType.String);
StoredProcedureCommand.AddInput(c, "@Colonia", draft.Colonia, DbType.String);
StoredProcedureCommand.AddInput(c, "@Ciudad", draft.Ciudad, DbType.String);
StoredProcedureCommand.AddInput(c, "@CveSATEstado", draft.CveSATEstado, DbType.Int32);
StoredProcedureCommand.AddInput(c, "@Pais", draft.Pais, DbType.String);
StoredProcedureCommand.AddInput(c, "@CP", draft.CP, DbType.String);
StoredProcedureCommand.AddInput(c, "@Telefono", draft.Telefono, DbType.String);
StoredProcedureCommand.AddInput(c, "@HashToken", tokenHash, DbType.StringFixedLength);
StoredProcedureCommand.AddInput(c, "@HorasExpiracion", 24, DbType.Int32);
});
}
public DbResult<bool> VerifyEmail(string tokenHash, string domainName)
{ return Execute("dbo.PA_Sis_CorreoVerify", 0, domainName, c => StoredProcedureCommand.AddInput(c, "@HashToken", tokenHash, DbType.StringFixedLength)); }
private DbResult<List<T>> QueryList<T>(string procedure, int user, string domain, System.Action<IDbCommand> parameters) where T : class, new()
{
return _db.Execute(domain, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedure, user); if (parameters != null) parameters(command);
List<T> value; using (IDataReader reader = command.ExecuteReader()) { value = reader.ToList<T>(); }
StoredProcedureCommand.EnsureSuccess(command); return value;
}
});
}
private DbResult<T> QueryOne<T>(string procedure, int user, string domain, System.Action<IDbCommand> parameters) where T : class, new()
{
return _db.Execute(domain, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedure, user); if (parameters != null) parameters(command);
T value; using (IDataReader reader = command.ExecuteReader()) { value = reader.ToObject<T>(); }
StoredProcedureCommand.EnsureSuccess(command); return value;
}
});
}
private DbResult<bool> Execute(string procedure, int user, string domain, System.Action<IDbCommand> parameters)
{
return _db.Execute(domain, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedure, user); if (parameters != null) parameters(command);
command.ExecuteNonQuery(); StoredProcedureCommand.EnsureSuccess(command); return true;
}
});
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Data;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Security;
using SIG.Web.AppCode.Services.Database;
using SIG.Web.AppCode.Utilities;
namespace SIG.Web.AppCode.Repositories.Security
{
public sealed class UserRepository : IUserRepository
{
private readonly IDbExecutor _dbExecutor;
public UserRepository(IDbExecutor dbExecutor)
{
_dbExecutor = dbExecutor;
}
public DbResult<UserModel> ObtenerPorNombreUsuario(string username, string domainName)
{
return _dbExecutor.Execute(domainName, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioGetByName", 0);
StoredProcedureCommand.AddInput(command, "@NombreUsuario", username, DbType.String);
UserModel user;
using (IDataReader reader = command.ExecuteReader())
{
user = reader.ToObject<UserModel>();
}
StoredProcedureCommand.EnsureSuccess(command);
return user;
}
});
}
public DbResult<bool> RegisterSuccessfulLogin(int cveUser, string domainName)
{
return ExecuteUserUpdate("dbo.PA_Sis_UsuarioLoginSuccessUpdate", cveUser, domainName);
}
public DbResult<bool> RegisterFailedLogin(int cveUser, string domainName)
{
return ExecuteUserUpdate("dbo.PA_Sis_UsuarioLoginFailedUpdate", cveUser, domainName);
}
private DbResult<bool> ExecuteUserUpdate(string procedureName, int cveUser, string domainName)
{
return _dbExecutor.Execute(domainName, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, procedureName, cveUser);
StoredProcedureCommand.AddInput(command, "@CveUsuario", cveUser, DbType.Int32);
command.ExecuteNonQuery();
StoredProcedureCommand.EnsureSuccess(command);
return true;
}
});
}
}
}

View File

@@ -0,0 +1,54 @@
using System.Data;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Services.Database;
namespace SIG.Web.AppCode.Repositories.Security
{
public sealed class UserSessionRepository : IUserSessionRepository
{
private readonly IDbExecutor _dbExecutor;
public UserSessionRepository(IDbExecutor dbExecutor)
{
_dbExecutor = dbExecutor;
}
public DbResult<bool> StartSession(int cveUser, string sessionToken, string ipAddress, string userAgent, string domainName)
{
return _dbExecutor.Execute(domainName, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioSesionAdd", cveUser);
StoredProcedureCommand.AddInput(command, "@CveUsuario", cveUser, DbType.Int32);
StoredProcedureCommand.AddInput(command, "@TokenSesion", sessionToken, DbType.String);
StoredProcedureCommand.AddInput(command, "@DireccionIP", ipAddress, DbType.String);
StoredProcedureCommand.AddInput(command, "@AgenteUsuario", userAgent, DbType.String);
StoredProcedureCommand.AddInput(command, "@NombreDominio", domainName, DbType.String);
command.ExecuteNonQuery();
StoredProcedureCommand.EnsureSuccess(command);
return true;
}
});
}
public DbResult<bool> EndSession(string sessionToken, string domainName)
{
return _dbExecutor.Execute(domainName, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_UsuarioSesionEnd", 0);
StoredProcedureCommand.AddInput(command, "@TokenSesion", sessionToken, DbType.String);
command.ExecuteNonQuery();
StoredProcedureCommand.EnsureSuccess(command);
return true;
}
});
}
}
}

View File

@@ -0,0 +1,109 @@
using SIG.Web.AppCode.Configuration;
using SIG.Web.AppCode.Services.Logging;
using SIG.Web.AppCode.Session;
using System;
using System.Web;
using System.Web.UI;
namespace SIG.Web.AppCode.Security
{
public abstract class ClsPaginaBase : Page
{
protected UserSessionModel CurrentUser { get; private set; }
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
string currentPath = Request.AppRelativeCurrentExecutionFilePath;
if (IsPublicPage(currentPath))
{
return;
}
CurrentUser = AppServices.UserContext.GetCurrentUser();
if (CurrentUser == null)
{
Response.Redirect(BuildLoginUrl(currentPath), false);
Context.ApplicationInstance.CompleteRequest();
return;
}
if (!IsHomePage(currentPath) &&
!AppServices.PermissionService.CanAccessPage(CurrentUser.CveUsuario, currentPath))
{
Response.Redirect("~/AccessDenied.aspx", false);
Context.ApplicationInstance.CompleteRequest();
}
}
private string BuildLoginUrl(string currentPath)
{
string loginUrl = ResolveUrl("~/Login.aspx");
if (IsHomePage(currentPath))
{
return loginUrl;
}
string retornoURL =
Request.Url == null
? null
: Request.Url.PathAndQuery;
if (string.IsNullOrWhiteSpace(retornoURL) ||
retornoURL.Equals("/", StringComparison.OrdinalIgnoreCase))
{
return loginUrl;
}
return loginUrl +
"?retornoURL=" +
HttpUtility.UrlEncode(retornoURL);
}
private static bool IsHomePage(string appRelativePath)
{
return string.IsNullOrWhiteSpace(appRelativePath) ||
appRelativePath.Equals("~/Inicio.aspx", StringComparison.OrdinalIgnoreCase);
}
private static bool IsPublicPage(string appRelativePath)
{
return appRelativePath != null &&
(appRelativePath.Equals("~/Login.aspx", StringComparison.OrdinalIgnoreCase) ||
appRelativePath.Equals("~/Logout.aspx", StringComparison.OrdinalIgnoreCase) ||
appRelativePath.Equals("~/AccessDenied.aspx", StringComparison.OrdinalIgnoreCase));
}
protected override void OnError(EventArgs e)
{
Exception exception = Server.GetLastError();
if (exception == null)
{
base.OnError(e);
return;
}
ExceptionLogger.Log(
exception,
GetType().FullName
);
string message =
ErrorHandlerConfig.GetFriendlyMessage(exception);
Server.ClearError();
Site siteMaster = Master as Site;
if (siteMaster != null)
{
siteMaster.ShowGlobalError(message);
}
}
}
}

View File

@@ -0,0 +1,12 @@
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Security
{
public sealed class DummyPermissionService : IPermissionService
{
public bool CanAccess(UserSessionModel user, string appRelativePath)
{
return user != null;
}
}
}

View File

@@ -0,0 +1,9 @@
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Security
{
public interface IPermissionService
{
bool CanAccess(UserSessionModel user, string appRelativePath);
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Configuration;
using System.Data;
using Microsoft.Data.SqlClient;
using Npgsql;
using SIG.Web.AppCode.Contracts.Data;
namespace SIG.Web.AppCode.Services.Data
{
public sealed class DbConnectionFactory : IDbConnectionFactory
{
private readonly IDomainConnectionResolver _resolver;
public DbConnectionFactory(IDomainConnectionResolver resolver)
{
_resolver = resolver;
}
public IDbConnection Create(string domain)
{
var settings = _resolver.Resolve(domain);
var connection = ConfigurationManager.ConnectionStrings[settings.ConnectionStringName];
if (connection == null)
throw new InvalidOperationException("Connection string not found: " + settings.ConnectionStringName);
switch (settings.Provider)
{
case DbProviderKind.SqlServer:
return new SqlConnection(connection.ConnectionString);
case DbProviderKind.PostgreSql:
return new NpgsqlConnection(connection.ConnectionString);
default:
throw new NotSupportedException("Unsupported database provider: " + settings.Provider);
}
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Configuration;
using SIG.Web.AppCode.Contracts.Data;
namespace SIG.Web.AppCode.Services.Data
{
public sealed class DomainConnectionResolver : IDomainConnectionResolver
{
public DomainConnectionSettings Resolve(string domain)
{
// Default first. Later this can read a table or config mapping by host/domain.
var providerText = ConfigurationManager.AppSettings["DefaultDbProvider"] ?? "SqlServer";
var connectionName = ConfigurationManager.AppSettings["DefaultConnectionStringName"] ?? "SIG_DefaultSql";
DbProviderKind provider;
if (!Enum.TryParse(providerText, true, out provider))
provider = DbProviderKind.SqlServer;
return new DomainConnectionSettings
{
Domain = domain,
Provider = provider,
ConnectionStringName = connectionName,
IsDefault = true
};
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using SIG.Web.AppCode.Contracts.Database;
namespace SIG.Web.AppCode.Services.Database
{
public sealed class DbConnectionFactory : IDbConnectionFactory
{
public IDbConnection CreateConnection(string domainName)
{
string name = ConfigurationManager.AppSettings["DefaultConnectionStringName"];
if (string.IsNullOrWhiteSpace(name)) name = "SIG_DefaultSql";
ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[name];
if (settings == null) throw new InvalidOperationException("Connection string not found: " + name);
return new SqlConnection(settings.ConnectionString);
}
}
}

View File

@@ -0,0 +1,162 @@
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Services.Logging;
using System;
using System.Data;
using System.Data.SqlClient;
namespace SIG.Web.AppCode.Services.Database
{
public sealed class DbExecutor : IDbExecutor
{
private readonly IDbConnectionFactory _connectionFactory;
public DbExecutor(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public DbResult<T> Execute<T>(string domainName, Func<IDbConnection, T> action)
{
IDbConnection connection = null;
SqlConnection sqlConnection = null;
SqlInfoMessageEventHandler infoMessageHandler = null;
try
{
if (action == null)
{
throw new ArgumentNullException("action");
}
connection = _connectionFactory.CreateConnection(domainName);
sqlConnection = connection as SqlConnection;
if (sqlConnection != null)
{
/*
* No activar FireInfoMessageEventOnUserErrors: al hacerlo, SQL Server
* degrada errores de severidad 11-16 a InfoMessage y evita el catch.
* Los RAISERROR informativos de severidad 10 llegan al evento sin esa opción.
*/
infoMessageHandler = delegate(object sender, SqlInfoMessageEventArgs eventArgs)
{
foreach (SqlError error in eventArgs.Errors)
{
ExceptionLogger.Log(
CreateDiagnosticException(new InvalidOperationException(error.Message)),
"DbExecutor.SqlInfoMessage");
}
};
sqlConnection.InfoMessage += infoMessageHandler;
}
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
T data = action(connection);
return DbResult<T>.Ok(data);
}
catch (StoredProcedureException ex)
{
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.StoredProcedure", ex.UserMessage);
return DbResult<T>.Fail(ex.UserMessage, BuildTechnicalMessage(ex), ex);
}
catch (SqlException ex)
{
string userMessage;
switch (ex.Number)
{
case 208:
userMessage = "Falta una tabla, vista o procedimiento requerido en la base de datos.";
break;
case 207:
userMessage = "Falta una columna requerida en la base de datos.";
break;
case 2812:
userMessage = "Falta un procedimiento requerido en la base de datos.";
break;
case 4060:
case 18456:
userMessage = "No fue posible abrir la base de datos configurada.";
break;
case -2:
userMessage = "La base de datos tardó demasiado en responder.";
break;
default:
userMessage = "Ocurrió un error al consultar la base de datos.";
break;
}
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.SqlException", userMessage);
return DbResult<T>.Fail(userMessage, BuildTechnicalMessage(ex), ex);
}
catch (TimeoutException ex)
{
const string userMessage = "La base de datos tardó demasiado en responder. Intente nuevamente.";
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.TimeoutException", userMessage);
return DbResult<T>.Fail(userMessage, BuildTechnicalMessage(ex), ex);
}
catch (Exception ex)
{
const string userMessage = "Ocurrió un error al consultar la base de datos.";
ExceptionLogger.Log(CreateDiagnosticException(ex), "DbExecutor.Exception", userMessage);
return DbResult<T>.Fail(userMessage, BuildTechnicalMessage(ex), ex);
}
finally
{
if (sqlConnection != null && infoMessageHandler != null)
{
sqlConnection.InfoMessage -= infoMessageHandler;
}
if (connection != null)
{
try
{
if (connection.State != ConnectionState.Closed)
{
connection.Close();
}
}
finally
{
connection.Dispose();
}
}
StoredProcedureDiagnostics.Clear();
}
}
private static Exception CreateDiagnosticException(Exception exception)
{
string diagnostic = StoredProcedureDiagnostics.BuildDiagnostic();
if (string.IsNullOrWhiteSpace(diagnostic))
{
return exception;
}
return new InvalidOperationException(
exception.Message + Environment.NewLine + diagnostic,
exception);
}
private static string BuildTechnicalMessage(Exception exception)
{
string diagnostic = StoredProcedureDiagnostics.BuildDiagnostic();
return string.IsNullOrWhiteSpace(diagnostic)
? exception.Message
: exception.Message + Environment.NewLine + diagnostic;
}
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Data;
using SIG.Web.AppCode.Models.Common;
namespace SIG.Web.AppCode.Services.Database
{
public static class StoredProcedureCommand
{
public static void Configure(IDbCommand command, string procedureName, int cveUsuarioEjecuta)
{
if (command == null)
{
throw new ArgumentNullException("command");
}
StoredProcedureDiagnostics.Start(procedureName);
command.CommandText = procedureName;
command.CommandType = CommandType.StoredProcedure;
AddInput(command, "@CveUsuarioEjecuta", cveUsuarioEjecuta, DbType.Int32);
AddOutput(command, "@ErrorMostrar", DbType.String, -1);
AddOutput(command, "@ErrorTipo", DbType.Int32, 0);
AddOutput(command, "@Return", DbType.Boolean, 0);
}
public static IDbDataParameter AddInput(IDbCommand command, string name, object value)
{
return AddInput(command, name, value, null);
}
public static IDbDataParameter AddInput(IDbCommand command, string name, object value, DbType? dbType)
{
IDbDataParameter parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.Direction = ParameterDirection.Input;
parameter.Value = value ?? DBNull.Value;
if (dbType.HasValue)
{
parameter.DbType = dbType.Value;
}
command.Parameters.Add(parameter);
StoredProcedureDiagnostics.AddParameter(
parameter.ParameterName,
parameter.Value,
parameter.DbType,
parameter.Direction);
return parameter;
}
public static StoredProcedureResult ReadResult(IDbCommand command)
{
StoredProcedureDiagnostics.CaptureOutputs(command);
return new StoredProcedureResult
{
Return = ReadBoolean(command, "@Return"),
ErrorTipo = ReadInteger(command, "@ErrorTipo"),
ErrorMostrar = ReadString(command, "@ErrorMostrar")
};
}
public static void EnsureSuccess(IDbCommand command)
{
StoredProcedureResult result = ReadResult(command);
if (result.Return)
{
return;
}
object returnValue = ((IDataParameter)command.Parameters["@Return"]).Value;
bool outputsReceived = returnValue != null && returnValue != DBNull.Value;
string message;
if (!string.IsNullOrWhiteSpace(result.ErrorMostrar))
{
message = result.ErrorMostrar;
}
else if (!outputsReceived)
{
message = "La versión instalada del procedimiento no devolvió los parámetros requeridos; favor de contactar a Sistemas.";
}
else
{
message = "La operación no pudo completarse.";
}
throw new StoredProcedureException(message, result.ErrorTipo);
}
private static void AddOutput(IDbCommand command, string name, DbType dbType, int size)
{
IDbDataParameter parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.Direction = ParameterDirection.Output;
parameter.DbType = dbType;
if (size != 0)
{
parameter.Size = size;
}
command.Parameters.Add(parameter);
StoredProcedureDiagnostics.AddParameter(
parameter.ParameterName,
parameter.Value,
parameter.DbType,
parameter.Direction);
}
private static bool ReadBoolean(IDbCommand command, string name)
{
object value = ((IDataParameter)command.Parameters[name]).Value;
return value != null && value != DBNull.Value && Convert.ToBoolean(value);
}
private static int ReadInteger(IDbCommand command, string name)
{
object value = ((IDataParameter)command.Parameters[name]).Value;
return value == null || value == DBNull.Value ? 0 : Convert.ToInt32(value);
}
private static string ReadString(IDbCommand command, string name)
{
object value = ((IDataParameter)command.Parameters[name]).Value;
return value == null || value == DBNull.Value ? string.Empty : Convert.ToString(value);
}
}
}

View File

@@ -0,0 +1,276 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
namespace SIG.Web.AppCode.Services.Database
{
public static class StoredProcedureDiagnostics
{
private static readonly AsyncLocal<DiagnosticContext> CurrentContext =
new AsyncLocal<DiagnosticContext>();
public static void Start(string procedureName)
{
CurrentContext.Value = new DiagnosticContext
{
ProcedureName = procedureName
};
}
public static void AddParameter(string name, object value, DbType dbType, ParameterDirection direction)
{
DiagnosticContext context = CurrentContext.Value;
if (context == null)
{
return;
}
context.Parameters.Add(new DiagnosticParameter
{
Name = name,
Value = value,
DbType = dbType,
Direction = direction
});
}
public static void CaptureOutputs(IDbCommand command)
{
DiagnosticContext context = CurrentContext.Value;
if (context == null || command == null)
{
return;
}
foreach (IDataParameter commandParameter in command.Parameters)
{
if (commandParameter.Direction == ParameterDirection.Input)
{
continue;
}
DiagnosticParameter parameter = context.Parameters.FirstOrDefault(
item => item.Name.Equals(commandParameter.ParameterName, StringComparison.OrdinalIgnoreCase));
if (parameter != null)
{
parameter.Value = commandParameter.Value;
}
}
}
public static string BuildDiagnostic()
{
DiagnosticContext context = CurrentContext.Value;
if (context == null || string.IsNullOrWhiteSpace(context.ProcedureName))
{
return string.Empty;
}
var result = new StringBuilder();
result.AppendLine("StoredProcedure: " + context.ProcedureName);
result.AppendLine("Parametros:");
foreach (DiagnosticParameter parameter in context.Parameters)
{
result.AppendLine(parameter.Name + "=" + FormatSqlValue(parameter));
}
result.AppendLine("ReplaySql:");
AppendReplaySql(result, context);
return result.ToString();
}
public static void Clear()
{
CurrentContext.Value = null;
}
private static void AppendReplaySql(StringBuilder result, DiagnosticContext context)
{
IList<DiagnosticParameter> outputs = context.Parameters
.Where(parameter => parameter.Direction != ParameterDirection.Input)
.ToList();
foreach (DiagnosticParameter output in outputs)
{
result.Append("DECLARE ")
.Append(output.Name)
.Append(' ')
.Append(GetSqlType(output.DbType))
.AppendLine(";");
}
result.Append("EXEC ").Append(QuoteProcedureName(context.ProcedureName));
for (int index = 0; index < context.Parameters.Count; index++)
{
DiagnosticParameter parameter = context.Parameters[index];
result.Append(index == 0 ? Environment.NewLine + " " : "," + Environment.NewLine + " ");
result.Append(parameter.Name).Append(" = ");
if (parameter.Direction == ParameterDirection.Input)
{
result.Append(FormatSqlValue(parameter));
}
else
{
result.Append(parameter.Name).Append(" OUTPUT");
}
}
result.AppendLine(";");
if (outputs.Count > 0)
{
result.Append("SELECT ");
for (int index = 0; index < outputs.Count; index++)
{
if (index > 0)
{
result.Append(", ");
}
result.Append(outputs[index].Name);
}
result.AppendLine(";");
}
}
private static string FormatSqlValue(DiagnosticParameter parameter)
{
if (IsSensitive(parameter.Name))
{
return "N'[REDACTADO]'";
}
object value = parameter.Value;
if (value == null || value == DBNull.Value)
{
return "NULL";
}
if (value is bool)
{
return (bool)value ? "1" : "0";
}
if (value is DateTime)
{
return "'" + ((DateTime)value).ToString("yyyy-MM-ddTHH:mm:ss.fff", CultureInfo.InvariantCulture) + "'";
}
if (value is DateTimeOffset)
{
return "'" + ((DateTimeOffset)value).ToString("o", CultureInfo.InvariantCulture) + "'";
}
if (value is Guid)
{
return "'" + value + "'";
}
if (value is byte[])
{
byte[] bytes = (byte[])value;
int length = Math.Min(bytes.Length, 64);
return "0x" + BitConverter.ToString(bytes, 0, length).Replace("-", string.Empty)
+ (bytes.Length > length ? "/*TRUNCADO*/" : string.Empty);
}
if (value is string || value is char)
{
string text = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
if (text.Length > 1000)
{
text = text.Substring(0, 1000) + " [TRUNCADO]";
}
return "N'" + text.Replace("'", "''") + "'";
}
if (value is float || value is double || value is decimal ||
value is byte || value is sbyte || value is short || value is ushort ||
value is int || value is uint || value is long || value is ulong)
{
return Convert.ToString(value, CultureInfo.InvariantCulture);
}
string fallback = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
return "N'" + fallback.Replace("'", "''") + "'";
}
private static bool IsSensitive(string parameterName)
{
string name = (parameterName ?? string.Empty).ToLowerInvariant();
return name.Contains("contrasena") ||
name.Contains("password") ||
name.Contains("hash") ||
name.Contains("token") ||
name.Contains("secreto") ||
name.Contains("secret") ||
name.Contains("apikey") ||
name.Contains("api_key");
}
private static string GetSqlType(DbType dbType)
{
switch (dbType)
{
case DbType.Boolean: return "bit";
case DbType.Byte: return "tinyint";
case DbType.Int16: return "smallint";
case DbType.Int32: return "int";
case DbType.Int64: return "bigint";
case DbType.Decimal: return "decimal(38, 10)";
case DbType.Double: return "float";
case DbType.Single: return "real";
case DbType.Date: return "date";
case DbType.DateTime:
case DbType.DateTime2: return "datetime2(3)";
case DbType.DateTimeOffset: return "datetimeoffset(7)";
case DbType.Guid: return "uniqueidentifier";
case DbType.Binary: return "varbinary(max)";
case DbType.AnsiString: return "varchar(max)";
default: return "nvarchar(max)";
}
}
private static string QuoteProcedureName(string procedureName)
{
return string.Join(".", (procedureName ?? string.Empty)
.Split('.')
.Select(part => "[" + part.Replace("]", "]]" ) + "]"));
}
private sealed class DiagnosticContext
{
public DiagnosticContext()
{
Parameters = new List<DiagnosticParameter>();
}
public string ProcedureName { get; set; }
public IList<DiagnosticParameter> Parameters { get; private set; }
}
private sealed class DiagnosticParameter
{
public string Name { get; set; }
public object Value { get; set; }
public DbType DbType { get; set; }
public ParameterDirection Direction { get; set; }
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace SIG.Web.AppCode.Services.Database
{
public sealed class StoredProcedureException : Exception
{
public StoredProcedureException(string userMessage, int errorType)
: base(userMessage)
{
UserMessage = userMessage;
ErrorType = errorType;
}
public string UserMessage { get; private set; }
public int ErrorType { get; private set; }
}
}

View File

@@ -0,0 +1,720 @@
using System;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Hosting;
using SIG.Web.AppCode.Configuration;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Services.Errors
{
public sealed class SIGErrorRegistro
{
public string Codigo { get; set; }
public DateTime Fecha { get; set; }
public string Origen { get; set; }
public string CveUsuario { get; set; }
public string NombreUsuario { get; set; }
public string MetodoHttp { get; set; }
public string Ruta { get; set; }
public string Ip { get; set; }
public string AgenteUsuario { get; set; }
public bool EsPostbackAsincrono { get; set; }
public string DetalleTecnico { get; set; }
}
public interface ISIGErrorStore
{
void Guardar(SIGErrorRegistro registro);
void MarcarReportado(
string codigo,
DateTime fecha,
string cveUsuario,
string nombreUsuario,
string ruta);
}
public sealed class SIGTextFileErrorStore : ISIGErrorStore
{
private static readonly object BloqueoArchivo = new object();
public void Guardar(SIGErrorRegistro registro)
{
if (registro == null)
{
return;
}
StringBuilder texto = new StringBuilder();
texto.AppendLine();
texto.AppendLine("============================================================");
texto.AppendLine("ERROR");
texto.AppendLine("============================================================");
texto.AppendLine("Codigo: " + Safe(registro.Codigo));
texto.AppendLine("Fecha: " + registro.Fecha.ToString("yyyy-MM-dd HH:mm:ss.fff"));
texto.AppendLine("Origen: " + Safe(registro.Origen));
texto.AppendLine("CveUsuario: " + Safe(registro.CveUsuario));
texto.AppendLine("Nombre: " + Safe(registro.NombreUsuario));
texto.AppendLine("Metodo: " + Safe(registro.MetodoHttp));
texto.AppendLine("Ruta: " + Safe(registro.Ruta));
texto.AppendLine("IP: " + Safe(registro.Ip));
texto.AppendLine("AgenteUsuario: " + Safe(registro.AgenteUsuario));
texto.AppendLine("AsyncPostBack: " + registro.EsPostbackAsincrono);
texto.AppendLine();
texto.AppendLine("Detalle tecnico:");
texto.AppendLine(Safe(registro.DetalleTecnico));
texto.AppendLine("============================================================");
Escribir(texto.ToString());
}
public void MarcarReportado(
string codigo,
DateTime fecha,
string cveUsuario,
string nombreUsuario,
string ruta)
{
StringBuilder texto = new StringBuilder();
texto.AppendLine();
texto.AppendLine("------------------------------------------------------------");
texto.AppendLine("REPORTE DEL USUARIO");
texto.AppendLine("Codigo: " + Safe(codigo));
texto.AppendLine("Fecha: " + fecha.ToString("yyyy-MM-dd HH:mm:ss.fff"));
texto.AppendLine("CveUsuario: " + Safe(cveUsuario));
texto.AppendLine("Nombre: " + Safe(nombreUsuario));
texto.AppendLine("Ruta: " + Safe(ruta));
texto.AppendLine("------------------------------------------------------------");
Escribir(texto.ToString());
}
private static void Escribir(string texto)
{
string ruta = HostingEnvironment.MapPath("~/log/log.txt");
if (string.IsNullOrWhiteSpace(ruta))
{
ruta = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
"log",
"log.txt");
}
string directorio = Path.GetDirectoryName(ruta);
lock (BloqueoArchivo)
{
if (!Directory.Exists(directorio))
{
Directory.CreateDirectory(directorio);
}
using (FileStream stream = new FileStream(
ruta,
FileMode.Append,
FileAccess.Write,
FileShare.Read))
using (StreamWriter writer = new StreamWriter(
stream,
new UTF8Encoding(false)))
{
writer.Write(texto);
}
}
}
private static string Safe(string value)
{
return string.IsNullOrEmpty(value) ? string.Empty : value;
}
}
public static class SIGErrorManager
{
private const string ErrorCodeDataKey = "SIG.ErrorCode";
private static ISIGErrorStore _store =
new SIGTextFileErrorStore();
public static ISIGErrorStore Store
{
get { return _store; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_store = value;
}
}
public static string Registrar(
Exception exception,
HttpContext context,
string origen,
string cveUsuario,
string nombreUsuario)
{
if (exception == null)
{
exception = new Exception(
"No se recibio la excepcion original.");
}
string codigoExistente =
ObtenerCodigoExistente(exception);
if (!string.IsNullOrWhiteSpace(codigoExistente))
{
return codigoExistente;
}
string codigo =
"SIG-" +
DateTime.Now.ToString("yyyyMMdd") +
"-" +
Guid.NewGuid()
.ToString("N")
.Substring(0, 6)
.ToUpperInvariant();
AsignarCodigo(exception, codigo);
string metodo = string.Empty;
string ruta = string.Empty;
string ip = string.Empty;
string userAgent = string.Empty;
bool esAsync = false;
try
{
if (context != null && context.Request != null)
{
metodo = context.Request.HttpMethod;
ruta = context.Request.RawUrl;
ip = context.Request.UserHostAddress;
userAgent = context.Request.UserAgent;
esAsync = EsPeticionAsincrona(context);
}
}
catch
{
// El contexto no debe provocar otro error.
}
SIGErrorRegistro registro = new SIGErrorRegistro
{
Codigo = codigo,
Fecha = DateTime.Now,
Origen = origen,
CveUsuario = cveUsuario,
NombreUsuario = nombreUsuario,
MetodoHttp = metodo,
Ruta = ruta,
Ip = ip,
AgenteUsuario = userAgent,
EsPostbackAsincrono = esAsync,
DetalleTecnico = ObtenerDetalleTecnico(exception)
};
try
{
_store.Guardar(registro);
}
catch
{
// Nunca lanzar otro error desde el manejador global.
}
return codigo;
}
public static void MarcarReportado(
string codigo,
HttpContext context,
string cveUsuario,
string nombreUsuario)
{
string ruta = string.Empty;
try
{
if (context != null && context.Request != null)
{
ruta = context.Request.Form["ruta"];
if (string.IsNullOrWhiteSpace(ruta))
{
ruta = context.Request.RawUrl;
}
}
}
catch
{
ruta = string.Empty;
}
try
{
_store.MarcarReportado(
codigo,
DateTime.Now,
cveUsuario,
nombreUsuario,
ruta);
}
catch
{
// Nunca lanzar desde el registro de reporte.
}
}
public static void ObtenerUsuarioActual(
out string cveUsuario,
out string nombreUsuario,
out int cveRole)
{
cveUsuario = string.Empty;
nombreUsuario = string.Empty;
cveRole = 0;
try
{
if (AppServices.UserContext != null)
{
UserSessionModel usuario =
AppServices.UserContext.GetCurrentUser();
if (usuario != null)
{
cveUsuario =
Convert.ToString(usuario.CveUsuario);
nombreUsuario =
Convert.ToString(usuario.Nombre);
cveRole = usuario.CveRol;
return;
}
}
}
catch
{
// Si Session no esta disponible, continuar con Items.
}
try
{
HttpContext context = HttpContext.Current;
if (context == null)
{
return;
}
object itemCveUsuario =
context.Items["SIG.Error.CveUsuario"];
if (itemCveUsuario == null)
{
return;
}
cveUsuario =
Convert.ToString(itemCveUsuario);
nombreUsuario =
Convert.ToString(
context.Items["SIG.Error.NombreUsuario"]);
int.TryParse(
Convert.ToString(
context.Items["SIG.Error.CveRol"]),
out cveRole);
if (cveRole == 0)
{
int.TryParse(
Convert.ToString(
context.Items["SIG.Error.CveRol"]),
out cveRole);
}
}
catch
{
cveUsuario = string.Empty;
nombreUsuario = string.Empty;
cveRole = 0;
}
}
public static bool EsPeticionAsincrona(
HttpContext context)
{
if (context == null || context.Request == null)
{
return false;
}
try
{
string encabezado =
context.Request.Headers["X-MicrosoftAjax"];
if (!string.IsNullOrWhiteSpace(encabezado) &&
encabezado.IndexOf(
"Delta=true",
StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
return string.Equals(
context.Request.Form["__ASYNCPOST"],
"true",
StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
public static string CrearHtmlError(
string codigo,
Exception exception,
bool mostrarDetalle,
string cveUsuarioActual,
string nombreUsuarioActual,
int cveRoleActual)
{
string codigoSeguro =
HttpUtility.HtmlEncode(codigo);
string codigoJavaScript =
HttpUtility.JavaScriptStringEncode(codigo);
string cveUsuarioJavaScript =
HttpUtility.JavaScriptStringEncode(
cveUsuarioActual ?? string.Empty);
string nombreUsuarioSeguro =
HttpUtility.HtmlEncode(
nombreUsuarioActual ?? string.Empty);
string urlInicio =
VirtualPathUtility.ToAbsolute("~/Inicio.aspx");
string urlReporte =
VirtualPathUtility.ToAbsolute(
"~/ashx/ReportarError.ashx");
string detalleHtml = string.Empty;
if (mostrarDetalle && exception != null)
{
detalleHtml =
"<details class=\"sig-error-details\">" +
"<summary>Ver detalle tecnico</summary>" +
"<pre>" +
HttpUtility.HtmlEncode(
ObtenerDetalleTecnico(exception)) +
"</pre>" +
"</details>";
}
StringBuilder html = new StringBuilder();
html.AppendLine("<!DOCTYPE html>");
html.AppendLine("<html lang=\"es\">");
html.AppendLine("<head>");
html.AppendLine(" <meta charset=\"utf-8\">");
html.AppendLine(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
html.AppendLine(" <title>SIG - Error inesperado</title>");
html.AppendLine(" <link rel=\"shortcut icon\" href=\"/Imagenes/favicon.ico\">");
html.AppendLine(" <style>");
html.AppendLine(" html,body{margin:0;min-height:100%;font-family:'Open Sans',Arial,sans-serif;background:#eef1f5;color:#333}");
html.AppendLine(" *{box-sizing:border-box}");
html.AppendLine(" .sig-header{position:fixed;top:0;left:0;right:0;height:46px;z-index:10;display:flex;align-items:center;justify-content:space-between;padding:0 18px;background:#2b3643;color:#fff;box-shadow:0 1px 4px rgba(0,0,0,.18)}");
html.AppendLine(" .sig-brand{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:600}");
html.AppendLine(" .sig-brand-box{display:flex;align-items:center;justify-content:center;width:34px;height:30px;border-radius:3px;background:#3598dc;font-size:13px;font-weight:700;letter-spacing:.5px}");
html.AppendLine(" .sig-header-status{display:flex;flex-direction:column;align-items:flex-end;color:#c6cfda;font-size:12px;line-height:16px}");
html.AppendLine(" .sig-header-user{color:#fff;font-weight:600}");
html.AppendLine(" .sig-sidebar{position:fixed;top:46px;bottom:0;left:0;width:235px;padding-top:18px;background:#364150}");
html.AppendLine(" .sig-sidebar-title{padding:7px 20px 14px;color:#708096;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.6px}");
html.AppendLine(" .sig-restored-menu{margin:0;padding:0;list-style:none}");
html.AppendLine(" .sig-restored-menu li{margin:0;padding:0;list-style:none}");
html.AppendLine(" .sig-restored-menu a,.sig-restored-menu button{display:flex;align-items:center;gap:12px;width:100%;padding:12px 20px;border:0;background:transparent;color:#b4bcc8;font-family:inherit;font-size:13px;text-decoration:none;text-align:left;cursor:pointer}");
html.AppendLine(" .sig-restored-menu a:hover,.sig-restored-menu button:hover{background:#2c3542;color:#fff}");
html.AppendLine(" .sig-restored-menu .sub-menu{display:none;margin:0;padding:0;background:#2c3542}");
html.AppendLine(" .sig-restored-menu li.open>.sub-menu{display:block}");
html.AppendLine(" .sig-restored-menu .sub-menu a{padding-left:42px;font-size:12px}");
html.AppendLine(" .sig-main{min-height:100vh;margin-left:235px;padding:70px 28px 35px}");
html.AppendLine(" .sig-page-title{margin:0 0 14px;color:#666;font-size:22px;font-weight:300}");
html.AppendLine(" .sig-breadcrumb{margin-bottom:22px;padding:10px 14px;background:#fff;color:#888;font-size:12px}");
html.AppendLine(" .sig-error-card{max-width:820px;background:#fff;border:1px solid #e5e5e5;border-left:4px solid #e43a45;box-shadow:0 2px 5px rgba(0,0,0,.07)}");
html.AppendLine(" .sig-error-card-header{display:flex;align-items:center;gap:13px;padding:18px 22px;border-bottom:1px solid #eef1f5}");
html.AppendLine(" .sig-error-icon{display:flex;align-items:center;justify-content:center;width:38px;height:38px;flex:0 0 38px;border-radius:50%;background:#fbeaec;color:#e43a45;font-size:21px;font-weight:700}");
html.AppendLine(" .sig-error-title{margin:0 0 3px;color:#333;font-size:17px;font-weight:600}");
html.AppendLine(" .sig-error-subtitle{color:#777;font-size:13px}");
html.AppendLine(" .sig-error-body{padding:22px}");
html.AppendLine(" .sig-error-body p{margin:0 0 14px;line-height:1.6}");
html.AppendLine(" .sig-error-code-label{margin-bottom:6px;color:#777;font-size:12px;font-weight:600;text-transform:uppercase}");
html.AppendLine(" .sig-error-code{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 13px;background:#f4f6f8;border:1px solid #e7e9ec;border-radius:3px;font-family:Consolas,monospace;font-size:14px;font-weight:700}");
html.AppendLine(" .sig-copy-button{border:0;background:transparent;color:#3598dc;font-family:inherit;font-size:12px;cursor:pointer}");
html.AppendLine(" .sig-error-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px;margin-top:20px}");
html.AppendLine(" .sig-btn{min-height:34px;padding:7px 15px;border:1px solid transparent;border-radius:2px;font-family:inherit;font-size:13px;cursor:pointer;text-decoration:none}");
html.AppendLine(" .sig-btn-default{border-color:#d8d8d8;background:#fff;color:#555}");
html.AppendLine(" .sig-btn-primary{border-color:#258fd7;background:#3598dc;color:#fff}");
html.AppendLine(" .sig-btn-danger{border-color:#d52b37;background:#e43a45;color:#fff}");
html.AppendLine(" .sig-btn:disabled{cursor:wait;opacity:.6}");
html.AppendLine(" .sig-report-result{min-height:18px;margin-top:14px;color:#555;font-size:12px}");
html.AppendLine(" .sig-error-details{margin-top:20px;border-top:1px solid #eee;padding-top:15px}");
html.AppendLine(" .sig-error-details summary{color:#3598dc;font-size:13px;font-weight:600;cursor:pointer}");
html.AppendLine(" .sig-error-details pre{max-height:380px;margin:13px 0 0;padding:14px;overflow:auto;white-space:pre-wrap;word-break:break-word;border-radius:3px;background:#263238;color:#f2f2f2;font-family:Consolas,monospace;font-size:12px;line-height:1.45}");
html.AppendLine(" @media(max-width:800px){.sig-sidebar{display:none}.sig-main{margin-left:0;padding:65px 14px 25px}.sig-header-status{display:none}.sig-error-actions{flex-direction:column}.sig-btn{width:100%;text-align:center}}");
html.AppendLine(" </style>");
html.AppendLine("</head>");
html.AppendLine("<body>");
html.AppendLine("<header class=\"sig-header\">");
html.AppendLine(" <div class=\"sig-brand\">");
html.AppendLine(" <div class=\"sig-brand-box\">SIG</div>");
html.AppendLine(" <span>Sistema de Informacion Gerencial</span>");
html.AppendLine(" </div>");
html.AppendLine(" <div class=\"sig-header-status\">");
if (!string.IsNullOrWhiteSpace(nombreUsuarioSeguro))
{
html.AppendLine(" <span class=\"sig-header-user\">" + nombreUsuarioSeguro + "</span>");
}
html.AppendLine(" </div>");
html.AppendLine("</header>");
html.AppendLine("<aside class=\"sig-sidebar\">");
html.AppendLine(" <div class=\"sig-sidebar-title\">Navegacion</div>");
html.AppendLine(" <ul id=\"sigMenuRestaurado\" class=\"sig-restored-menu\">");
html.AppendLine(" <li><a href=\"" + HttpUtility.HtmlAttributeEncode(urlInicio) + "\"><span>&#8962;</span><span>Inicio</span></a></li>");
html.AppendLine(" <li><button type=\"button\" onclick=\"history.back();\"><span>&#8592;</span><span>Regresar</span></button></li>");
html.AppendLine(" </ul>");
html.AppendLine("</aside>");
html.AppendLine("<main class=\"sig-main\">");
html.AppendLine(" <h1 class=\"sig-page-title\">Error inesperado</h1>");
html.AppendLine(" <div class=\"sig-breadcrumb\">Inicio &nbsp;/&nbsp; Error</div>");
html.AppendLine(" <section class=\"sig-error-card\">");
html.AppendLine(" <div class=\"sig-error-card-header\">");
html.AppendLine(" <div class=\"sig-error-icon\">!</div>");
html.AppendLine(" <div>");
html.AppendLine(" <h2 class=\"sig-error-title\">No fue posible completar la operacion</h2>");
html.AppendLine(" <div class=\"sig-error-subtitle\">La aplicacion sigue disponible.</div>");
html.AppendLine(" </div>");
html.AppendLine(" </div>");
html.AppendLine(" <div class=\"sig-error-body\">");
html.AppendLine(" <p>Ocurrio un error inesperado al cargar esta seccion. Puedes regresar, intentar nuevamente o reportarlo al area de soporte.</p>");
html.AppendLine(" <div class=\"sig-error-code-label\">Codigo para soporte</div>");
html.AppendLine(" <div class=\"sig-error-code\">");
html.AppendLine(" <span id=\"sigCodigoError\">" + codigoSeguro + "</span>");
html.AppendLine(" <button type=\"button\" class=\"sig-copy-button\" onclick=\"sigCopiarCodigo();\">Copiar codigo</button>");
html.AppendLine(" </div>");
html.AppendLine(" <div id=\"sigResultadoReporte\" class=\"sig-report-result\"></div>");
html.AppendLine(detalleHtml);
html.AppendLine(" <div class=\"sig-error-actions\">");
html.AppendLine(" <button type=\"button\" class=\"sig-btn sig-btn-default\" onclick=\"history.back();\">Regresar</button>");
html.AppendLine(" <a class=\"sig-btn sig-btn-default\" href=\"" + HttpUtility.HtmlAttributeEncode(urlInicio) + "\">Ir al inicio</a>");
html.AppendLine(" <button type=\"button\" class=\"sig-btn sig-btn-primary\" onclick=\"location.reload();\">Reintentar</button>");
html.AppendLine(" <button type=\"button\" id=\"sigBtnReportar\" class=\"sig-btn sig-btn-danger\" onclick=\"sigReportarError();\">Reportar a soporte</button>");
html.AppendLine(" </div>");
html.AppendLine(" </div>");
html.AppendLine(" </section>");
html.AppendLine("</main>");
html.AppendLine("<script>");
html.AppendLine("var sigCveUsuarioSesion='" + cveUsuarioJavaScript + "';");
html.AppendLine("var sigCodigoError='" + codigoJavaScript + "';");
html.AppendLine("var sigUrlReporte='" + HttpUtility.JavaScriptStringEncode(urlReporte) + "';");
html.AppendLine("var sigMenuStorageKey='sig.currentMenu.v1';");
html.AppendLine("function sigBuscarSubMenu(elemento){");
html.AppendLine(" var hijos=elemento.children;");
html.AppendLine(" for(var i=0;i<hijos.length;i++){");
html.AppendLine(" if(hijos[i].tagName==='UL' && (' '+hijos[i].className+' ').indexOf(' sub-menu ')>=0){return hijos[i];}");
html.AppendLine(" }");
html.AppendLine(" return null;");
html.AppendLine("}");
html.AppendLine("function sigInicializarMenu(menu){");
html.AppendLine(" menu.onclick=function(evento){");
html.AppendLine(" evento=evento||window.event;");
html.AppendLine(" var enlace=evento.target||evento.srcElement;");
html.AppendLine(" while(enlace&&enlace!==menu&&enlace.tagName!=='A'){enlace=enlace.parentNode;}");
html.AppendLine(" if(!enlace||enlace===menu){return;}");
html.AppendLine(" var li=enlace.parentNode;");
html.AppendLine(" var subMenu=sigBuscarSubMenu(li);");
html.AppendLine(" if(!subMenu){return;}");
html.AppendLine(" if(evento.preventDefault){evento.preventDefault();}else{evento.returnValue=false;}");
html.AppendLine(" var abierto=(' '+li.className+' ').indexOf(' open ')>=0;");
html.AppendLine(" li.className=abierto?li.className.replace(/\\bopen\\b/g,'').replace(/^\\s+|\\s+$/g,''):(li.className+' open').replace(/^\\s+|\\s+$/g,'');");
html.AppendLine(" };");
html.AppendLine("}");
html.AppendLine("function sigRestaurarMenu(){");
html.AppendLine(" if(!sigCveUsuarioSesion){return;}");
html.AppendLine(" try{");
html.AppendLine(" var contenido=localStorage.getItem(sigMenuStorageKey);");
html.AppendLine(" if(!contenido){return;}");
html.AppendLine(" var datos=JSON.parse(contenido);");
html.AppendLine(" if(!datos||!datos.menuHtml||!datos.cveUsuario){return;}");
html.AppendLine(" if(String(datos.cveUsuario)!==String(sigCveUsuarioSesion)){return;}");
html.AppendLine(" var menu=document.getElementById('sigMenuRestaurado');");
html.AppendLine(" if(!menu){return;}");
html.AppendLine(" menu.innerHTML=datos.menuHtml;");
html.AppendLine(" sigInicializarMenu(menu);");
html.AppendLine(" }catch(ex){if(window.console&&console.warn){console.warn('No fue posible restaurar el menu de SIG.',ex);}}");
html.AppendLine("}");
html.AppendLine("function sigCopiarCodigo(){");
html.AppendLine(" if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(sigCodigoError);}else{");
html.AppendLine(" var temporal=document.createElement('textarea');");
html.AppendLine(" temporal.value=sigCodigoError;");
html.AppendLine(" document.body.appendChild(temporal);");
html.AppendLine(" temporal.select();");
html.AppendLine(" document.execCommand('copy');");
html.AppendLine(" document.body.removeChild(temporal);");
html.AppendLine(" }");
html.AppendLine(" document.getElementById('sigResultadoReporte').textContent='El codigo fue copiado.';");
html.AppendLine("}");
html.AppendLine("function sigReportarError(){");
html.AppendLine(" var boton=document.getElementById('sigBtnReportar');");
html.AppendLine(" var resultado=document.getElementById('sigResultadoReporte');");
html.AppendLine(" boton.disabled=true;");
html.AppendLine(" resultado.textContent='Enviando reporte...';");
html.AppendLine(" var xhr=new XMLHttpRequest();");
html.AppendLine(" xhr.open('POST',sigUrlReporte,true);");
html.AppendLine(" xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');");
html.AppendLine(" xhr.onreadystatechange=function(){");
html.AppendLine(" if(xhr.readyState!==4){return;}");
html.AppendLine(" if(xhr.status>=200&&xhr.status<300){resultado.textContent='El error fue reportado correctamente.';return;}");
html.AppendLine(" boton.disabled=false;");
html.AppendLine(" resultado.textContent='No fue posible enviar el reporte. Comunica el codigo a soporte.';");
html.AppendLine(" };");
html.AppendLine(" xhr.send('codigo='+encodeURIComponent(sigCodigoError)+'&ruta='+encodeURIComponent(window.location.href));");
html.AppendLine("}");
html.AppendLine("sigRestaurarMenu();");
html.AppendLine("</script>");
html.AppendLine("</body>");
html.AppendLine("</html>");
return html.ToString();
}
public static string ObtenerDetalleTecnico(
Exception exception)
{
if (exception == null)
{
return "No se encontro informacion de la excepcion.";
}
StringBuilder texto = new StringBuilder();
Exception actual = exception;
int nivel = 1;
while (actual != null)
{
texto.AppendLine(
"EXCEPCION " + nivel);
texto.AppendLine(
"Tipo: " + actual.GetType().FullName);
texto.AppendLine(
"Mensaje: " + actual.Message);
HttpParseException parseException =
actual as HttpParseException;
if (parseException != null)
{
texto.AppendLine(
"Archivo: " + parseException.VirtualPath);
texto.AppendLine(
"Linea: " + parseException.Line);
}
if (!string.IsNullOrWhiteSpace(
actual.StackTrace))
{
texto.AppendLine();
texto.AppendLine("Stack trace:");
texto.AppendLine(actual.StackTrace);
}
texto.AppendLine();
texto.AppendLine(
"--------------------------------------------------");
texto.AppendLine();
actual = actual.InnerException;
nivel++;
}
return texto.ToString();
}
private static string ObtenerCodigoExistente(
Exception exception)
{
Exception actual = exception;
while (actual != null)
{
try
{
object valor =
actual.Data[ErrorCodeDataKey];
if (valor != null)
{
return valor.ToString();
}
}
catch
{
}
actual = actual.InnerException;
}
return string.Empty;
}
private static void AsignarCodigo(
Exception exception,
string codigo)
{
Exception actual = exception;
while (actual != null)
{
try
{
actual.Data[ErrorCodeDataKey] = codigo;
}
catch
{
}
actual = actual.InnerException;
}
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Configuration;
using System.IO;
using System.Text;
using System.Web;
namespace SIG.Web.AppCode.Services.Logging
{
public static class ExceptionLogger
{
private static readonly object LockObject = new object();
public static void Log(Exception exception, string source)
{
Log(exception, source, null);
}
public static void Log(Exception exception, string source, string userMessage)
{
try
{
if (exception == null)
{
return;
}
string logPath = GetLogPath();
string directory = Path.GetDirectoryName(logPath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string content = BuildLogContent(exception, source, userMessage);
lock (LockObject)
{
File.AppendAllText(logPath, content, Encoding.UTF8);
}
}
catch
{
// Never throw from the logger.
// If the logger fails, the application must continue.
}
}
private static string GetLogPath()
{
string configuredPath = ConfigurationManager.AppSettings["ExceptionLogFilePath"];
if (string.IsNullOrWhiteSpace(configuredPath))
{
configuredPath = "~/log/log.txt";
}
if (HttpContext.Current != null)
{
return HttpContext.Current.Server.MapPath(configuredPath);
}
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log", "log.txt");
}
private static string BuildLogContent(Exception exception, string source, string userMessage)
{
var sb = new StringBuilder();
sb.AppendLine("============================================================");
sb.AppendLine("Date: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
sb.AppendLine("Source: " + Safe(source));
sb.AppendLine("UserMessage: " + Safe(userMessage));
HttpContext context = HttpContext.Current;
if (context != null && context.Request != null)
{
sb.AppendLine("Url: " + Safe(context.Request.Url != null ? context.Request.Url.ToString() : null));
sb.AppendLine("HttpMethod: " + Safe(context.Request.HttpMethod));
sb.AppendLine("DireccionIP: " + Safe(context.Request.UserHostAddress));
sb.AppendLine("AgenteUsuario: " + Safe(context.Request.UserAgent));
}
if (context != null && context.User != null && context.User.Identity != null)
{
sb.AppendLine("Identity: " + Safe(context.User.Identity.Name));
sb.AppendLine("IsAuthenticated: " + context.User.Identity.IsAuthenticated);
}
sb.AppendLine("ExceptionType: " + exception.GetType().FullName);
sb.AppendLine("Message: " + exception.Message);
sb.AppendLine("BaseException: " + exception.GetBaseException().Message);
sb.AppendLine("StackTrace:");
sb.AppendLine(exception.ToString());
sb.AppendLine();
return sb.ToString();
}
private static string Safe(string value)
{
return string.IsNullOrWhiteSpace(value) ? string.Empty : value;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using SIG.Web.AppCode.Contracts.Login;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Services.Login
{
public sealed class DummyLoginService : ILoginService
{
public LoginResult Login(string nombreUsuario, string contrasena, string nombreDominio)
{
if (string.IsNullOrWhiteSpace(nombreUsuario))
{
return LoginResult.Fail("Escriba un nombre de usuario.");
}
return LoginResult.Ok(new UserSessionModel
{
CveUsuario = 15,
CveRol = 1,
CveEmpresa = 1,
NombreUsuario = nombreUsuario.Trim(),
Nombre = "Usuario Demo",
CorreoElectronico = "usuario.demo@example.invalid",
NombreRol = "Administrador",
NombreDominio = nombreDominio,
TokenSesion = Guid.NewGuid().ToString("N"),
DebeCambiarContrasena = false
});
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Services.Menu
{
public sealed class DummyMenuProvider : IMenuProvider
{
public IReadOnlyList<MenuItemModel> GetMenu(UserSessionModel user)
{
return new List<MenuItemModel>
{
new MenuItemModel { NombreModulo = "Home", Url = "~/Inicio.aspx", ClaseCssIcono = "icon-home" },
new MenuItemModel { NombreModulo = "Developer tools", Url = "#", ClaseCssIcono = "icon-puzzle", Hijos =
{
new MenuItemModel { NombreModulo = "Blank page", Url = "~/Inicio.aspx", ClaseCssIcono = "icon-doc" }
}
}
};
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Services.Database;
using SIG.Web.AppCode.Utilities;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Services.Menu
{
public sealed class MenuProvider : IMenuProvider
{
private readonly IDbExecutor _dbExecutor;
public MenuProvider(IDbExecutor dbExecutor)
{
_dbExecutor = dbExecutor;
}
public IReadOnlyList<MenuItemModel> GetMenu(UserSessionModel user)
{
if (user == null)
{
return new List<MenuItemModel>();
}
var modulesResult = GetModules(user);
if (!modulesResult.Success || modulesResult.Data == null)
{
return new List<MenuItemModel>();
}
return BuildTree(modulesResult.Data);
}
private SIG.Web.AppCode.Models.Common.DbResult<List<MenuItemModel>> GetModules(UserSessionModel user)
{
return _dbExecutor.Execute(user.NombreDominio, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_MenuList", user.CveUsuario);
StoredProcedureCommand.AddInput(command, "@CveRol", user.CveRol, DbType.Int32);
List<MenuItemModel> items;
using (IDataReader reader = command.ExecuteReader())
{
items = reader.ToList<MenuItemModel>();
}
StoredProcedureCommand.EnsureSuccess(command);
return items;
}
});
}
private static IReadOnlyList<MenuItemModel> BuildTree(List<MenuItemModel> flatItems)
{
if (flatItems == null || flatItems.Count == 0)
{
return new List<MenuItemModel>();
}
var byId = flatItems.ToDictionary(x => x.CveModulo);
var roots = new List<MenuItemModel>();
foreach (var item in flatItems.OrderBy(x => x.OrdenVisualizacion).ThenBy(x => x.NombreModulo))
{
if (item.CveModuloPadre.HasValue && byId.ContainsKey(item.CveModuloPadre.Value))
{
byId[item.CveModuloPadre.Value].Hijos.Add(item);
}
else
{
roots.Add(item);
}
}
return roots;
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
using SIG.Web.AppCode.Contracts.Search;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Services.Search
{
public sealed class DummyBuscadorCatalogoDataProvider : IBuscadorCatalogoDataProvider
{
public BuscadorCatalogoCallbackRespuesta Search(BuscadorCatalogoCallbackSolicitud request, UserSessionModel user)
{
var response = new BuscadorCatalogoCallbackRespuesta { Total = 1 };
response.Rows.Add(new Dictionary<string, object>
{
["Id"] = 1,
["Descripcion"] = "Dummy result"
});
return response;
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Models.Security;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Services.Security
{
public sealed class LoginService : ILoginService
{
private const string GenericError = "No fue posible iniciar sesion. Verifique sus datos.";
private readonly IUserRepository _userRepository;
private readonly IPasswordHasher _passwordHasher;
private readonly ILoginAttemptRepository _loginAttemptRepository;
private readonly IUserSessionRepository _userSessionRepository;
public LoginService(
IUserRepository userRepository,
IPasswordHasher passwordHasher,
ILoginAttemptRepository loginAttemptRepository,
IUserSessionRepository userSessionRepository)
{
_userRepository = userRepository;
_passwordHasher = passwordHasher;
_loginAttemptRepository = loginAttemptRepository;
_userSessionRepository = userSessionRepository;
}
public LoginResult Login(LoginRequest request)
{
if (request == null || string.IsNullOrWhiteSpace(request.NombreUsuario) || string.IsNullOrWhiteSpace(request.Contrasena))
{
return LoginResult.Fail(GenericError);
}
string username = request.NombreUsuario.Trim();
var userResult = _userRepository.ObtenerPorNombreUsuario(username, request.NombreDominio);
if (!userResult.Success)
{
return LoginResult.Fail(userResult.UserMessage);
}
UserModel user = userResult.Data;
if (user == null)
{
_loginAttemptRepository.Save(null, username, false, "UserNotFound", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
return LoginResult.Fail(GenericError);
}
if (!user.CorreoVerificado)
{
_loginAttemptRepository.Save(user.CveUsuario, username, false, "EmailNotVerified", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
return LoginResult.Fail("Debes verificar tu correo electrónico antes de iniciar sesión.");
}
if (!user.Activo)
{
_loginAttemptRepository.Save(user.CveUsuario, username, false, "InactiveUser", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
return LoginResult.Fail("El usuario se encuentra inactivo.");
}
if (user.BloqueadoHasta.HasValue && user.BloqueadoHasta.Value > DateTime.Now) {
_loginAttemptRepository.Save(user.CveUsuario, username, false, "LockedUser", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
return LoginResult.Fail("El usuario se encuentra bloqueado temporalmente.");
}
if (!_passwordHasher.VerifyPassword(request.Contrasena, user.HashContrasena))
{
_userRepository.RegisterFailedLogin(user.CveUsuario, request.NombreDominio);
_loginAttemptRepository.Save(user.CveUsuario, username, false, "InvalidPassword", request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
return LoginResult.Fail(GenericError);
}
string sessionToken = Guid.NewGuid().ToString("N");
var loginUpdateResult = _userRepository.RegisterSuccessfulLogin(user.CveUsuario, request.NombreDominio);
if (!loginUpdateResult.Success)
{
return LoginResult.Fail(loginUpdateResult.UserMessage);
}
var sessionResult = _userSessionRepository.StartSession(user.CveUsuario, sessionToken, request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
if (!sessionResult.Success)
{
return LoginResult.Fail(sessionResult.UserMessage);
}
_loginAttemptRepository.Save(user.CveUsuario, username, true, null, request.DireccionIP, request.AgenteUsuario, request.NombreDominio);
var sessionUser = new UserSessionModel
{
CveUsuario = user.CveUsuario,
CveRol = user.CveRol,
NombreUsuario = user.NombreUsuario,
Nombre = user.NombreUsuario,
CorreoElectronico = user.CorreoElectronico,
NombreRol = user.NombreRol,
NombreDominio = request.NombreDominio,
TokenSesion = sessionToken,
DebeCambiarContrasena = user.DebeCambiarContrasena
};
return LoginResult.Ok(sessionUser);
}
public void Logout(string sessionToken, string domainName)
{
if (!string.IsNullOrWhiteSpace(sessionToken))
{
_userSessionRepository.EndSession(sessionToken, domainName);
}
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Security.Cryptography;
using SIG.Web.AppCode.Contracts.Security;
namespace SIG.Web.AppCode.Services.Security
{
public sealed class PasswordHasher : IPasswordHasher
{
private const int SaltSize = 16;
private const int HashSize = 32;
private const int Iterations = 100000;
public string HashPassword(string password)
{
byte[] salt = new byte[SaltSize];
using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(salt);
byte[] hash = Derive(password, salt, Iterations);
return "PBKDF2-SHA256$" + Iterations + "$" + Convert.ToBase64String(salt) + "$" + Convert.ToBase64String(hash);
}
public bool VerifyPassword(string password, string storedHash)
{
if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(storedHash)) return false;
string[] parts = storedHash.Split('$');
if (parts.Length != 4 || parts[0] != "PBKDF2-SHA256") return false;
int iterations; if (!int.TryParse(parts[1], out iterations)) return false;
byte[] salt = Convert.FromBase64String(parts[2]);
byte[] expected = Convert.FromBase64String(parts[3]);
byte[] actual = Derive(password, salt, iterations);
return FixedTimeEquals(actual, expected);
}
private static byte[] Derive(string password, byte[] salt, int iterations)
{
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256)) return pbkdf2.GetBytes(HashSize);
}
private static bool FixedTimeEquals(byte[] left, byte[] right)
{
if (left == null || right == null || left.Length != right.Length) return false;
int diff = 0; for (int i = 0; i < left.Length; i++) diff |= left[i] ^ right[i];
return diff == 0;
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Data;
using SIG.Web.AppCode.Contracts.Database;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Services.Database;
namespace SIG.Web.AppCode.Services.Security
{
public sealed class PermissionService : IPermissionService
{
private readonly IDbExecutor _dbExecutor;
public PermissionService(IDbExecutor dbExecutor)
{
_dbExecutor = dbExecutor;
}
public bool HasPermission(int cveUser, string permissionCode)
{
if (cveUser <= 0 || string.IsNullOrWhiteSpace(permissionCode))
{
return false;
}
var result = _dbExecutor.Execute(null, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_PermisoCheck", cveUser);
StoredProcedureCommand.AddInput(command, "@CodigoPermiso", permissionCode.Trim(), DbType.String);
object value = command.ExecuteScalar();
StoredProcedureCommand.EnsureSuccess(command);
return value != null && value != DBNull.Value && Convert.ToBoolean(value);
}
});
return result.Success && result.Data;
}
public bool CanAccessPage(int cveUser, string pageUrl)
{
if (cveUser <= 0)
{
return false;
}
string normalizedUrl = NormalizePageUrl(pageUrl);
if (IsPublicPage(normalizedUrl))
{
return true;
}
var result = _dbExecutor.Execute(null, connection =>
{
using (IDbCommand command = connection.CreateCommand())
{
StoredProcedureCommand.Configure(command, "dbo.PA_Sis_PaginaAccessCheck", cveUser);
StoredProcedureCommand.AddInput(command, "@PaginaUrl", normalizedUrl, DbType.String);
object value = command.ExecuteScalar();
StoredProcedureCommand.EnsureSuccess(command);
return value != null && value != DBNull.Value && Convert.ToBoolean(value);
}
});
return result.Success && result.Data;
}
private static string NormalizePageUrl(string pageUrl)
{
if (string.IsNullOrWhiteSpace(pageUrl))
{
return string.Empty;
}
string value = pageUrl.Trim();
if (value.StartsWith("~/", StringComparison.Ordinal))
{
value = value.Substring(2);
}
if (value.StartsWith("/", StringComparison.Ordinal))
{
value = value.Substring(1);
}
return value;
}
private static bool IsPublicPage(string normalizedUrl)
{
return normalizedUrl.Equals("Login.aspx", StringComparison.OrdinalIgnoreCase) ||
normalizedUrl.Equals("Logout.aspx", StringComparison.OrdinalIgnoreCase) ||
normalizedUrl.Equals("AccessDenied.aspx", StringComparison.OrdinalIgnoreCase) ||
normalizedUrl.Equals("Error.aspx", StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Models.Common;
using SIG.Web.AppCode.Models.Security;
namespace SIG.Web.AppCode.Services.Security
{
public sealed class PublicRegistrationService : IPublicRegistrationService
{
private readonly IUserAdministrationRepository _repository;
private readonly IPasswordHasher _passwordHasher;
private readonly IEmailSender _emailSender;
public PublicRegistrationService(IUserAdministrationRepository repository, IPasswordHasher passwordHasher, IEmailSender emailSender) { _repository = repository; _passwordHasher = passwordHasher; _emailSender = emailSender; }
public DbResult<bool> Validate(PublicRegistrationDraft draft, string domainName)
{
string error = ValidateRequired(draft);
return error == null ? _repository.ValidateRegistration(draft.NombreUsuario, draft.CorreoElectronico, draft.RFC, domainName) : DbResult<bool>.Fail(error, error, null);
}
public DbResult<List<StateOptionModel>> ListStates(string domainName) { return _repository.ListStates(domainName); }
public DbResult<bool> Register(PublicRegistrationDraft draft, string verificationBaseUrl, string domainName)
{
DbResult<bool> validation = Validate(draft, domainName); if (!validation.Success) return validation;
if (!_emailSender.IsConfigured) return DbResult<bool>.Fail("El envío de correo todavía no está configurado. Contacta a Sistemas.", "SMTP no configurado.", null);
string token = CreateToken();
DbResult<RegistrationCreatedModel> created = _repository.CreateRegistration(draft, _passwordHasher.HashPassword(draft.Contrasena), HashToken(token), domainName);
if (!created.Success) return DbResult<bool>.Fail(created.UserMessage, created.TechnicalMessage, created.Exception);
try
{
_emailSender.SendVerification(draft.CorreoElectronico, verificationBaseUrl + (verificationBaseUrl.Contains("?") ? "&" : "?") + "token=" + Uri.EscapeDataString(token));
return DbResult<bool>.Ok(true);
}
catch (Exception ex) { return DbResult<bool>.Fail("La cuenta quedó pendiente, pero no fue posible enviar el correo. Contacta a Sistemas para reenviarlo.", ex.Message, ex); }
}
public DbResult<bool> Verify(string token, string domainName)
{
return string.IsNullOrWhiteSpace(token) ? DbResult<bool>.Fail("La liga de verificación no es válida.", "Token vacío.", null) : _repository.VerifyEmail(HashToken(token), domainName);
}
private static string ValidateRequired(PublicRegistrationDraft d)
{
if (d == null) return "Captura los datos del registro.";
if (string.IsNullOrWhiteSpace(d.NombreUsuario) || string.IsNullOrWhiteSpace(d.CorreoElectronico) || string.IsNullOrWhiteSpace(d.Contrasena)) return "Completa los datos de usuario.";
if (d.Contrasena.Length < 8) return "La contraseña debe tener al menos 8 caracteres.";
if (string.IsNullOrWhiteSpace(d.RFC) || string.IsNullOrWhiteSpace(d.RazonSocial) || string.IsNullOrWhiteSpace(d.Direccion) || string.IsNullOrWhiteSpace(d.Colonia) || string.IsNullOrWhiteSpace(d.Ciudad) || d.CveSATEstado <= 0) return "Completa los datos obligatorios de la empresa.";
return null;
}
private static string CreateToken() { byte[] bytes = new byte[32]; using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(bytes); return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_'); }
private static string HashToken(string token) { using (SHA256 sha = SHA256.Create()) { byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(token)); var value = new StringBuilder(64); foreach (byte item in hash) value.Append(item.ToString("x2")); return value.ToString(); } }
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Configuration;
using System.Net;
using System.Net.Mail;
using SIG.Web.AppCode.Contracts.Security;
namespace SIG.Web.AppCode.Services.Security
{
public sealed class SmtpEmailSender : IEmailSender
{
public bool IsConfigured { get { return !string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["SmtpHost"]) && !string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["EmailFrom"]); } }
public void SendVerification(string recipient, string verificationUrl)
{
if (!IsConfigured) throw new InvalidOperationException("El envío de correo no se encuentra configurado.");
int port; if (!int.TryParse(ConfigurationManager.AppSettings["SmtpPort"], out port)) port = 25;
bool ssl; bool.TryParse(ConfigurationManager.AppSettings["SmtpEnableSsl"], out ssl);
using (var message = new MailMessage(ConfigurationManager.AppSettings["EmailFrom"], recipient))
using (var client = new SmtpClient(ConfigurationManager.AppSettings["SmtpHost"], port))
{
message.Subject = "Verifica tu correo - Plataforma de Desarrolladores";
message.IsBodyHtml = true;
message.Body = "<p>Para concluir la creación de tu cuenta, abre la siguiente liga:</p><p><a href=\"" + WebUtility.HtmlEncode(verificationUrl) + "\">Verificar mi correo</a></p><p>La liga vence en 24 horas.</p>";
client.EnableSsl = ssl;
string user = ConfigurationManager.AppSettings["SmtpUser"];
if (!string.IsNullOrWhiteSpace(user)) client.Credentials = new NetworkCredential(user, ConfigurationManager.AppSettings["SmtpPassword"]);
client.Send(message);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System.Web;
using SIG.Web.AppCode.Contracts.Security;
using SIG.Web.AppCode.Session;
namespace SIG.Web.AppCode.Services.Security
{
public sealed class WebFormsUserContextProvider : Contracts.Security.IUserContextProvider
{
public bool IsAuthenticated() { return GetCurrentUser() != null; }
public UserSessionModel GetCurrentUser()
{
return HttpContext.Current == null || HttpContext.Current.Session == null ? null : HttpContext.Current.Session[SessionKeys.CurrentUser] as UserSessionModel;
}
public void SetCurrentUser(UserSessionModel user)
{
if (HttpContext.Current == null || HttpContext.Current.Session == null)
return; HttpContext.Current.Session[SessionKeys.CurrentUser] = user;
HttpContext.Current.Session[SessionKeys.TokenSesion] = user != null ? user.TokenSesion : null;
}
public void Clear()
{
if (HttpContext.Current == null || HttpContext.Current.Session == null)
return; HttpContext.Current.Session.Remove(SessionKeys.CurrentUser);
HttpContext.Current.Session.Remove(SessionKeys.TokenSesion);
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace SIG.Web.AppCode.Session
{
public interface IMenuProvider
{
IReadOnlyList<MenuItemModel> GetMenu(UserSessionModel user);
}
}

View File

@@ -0,0 +1,10 @@
namespace SIG.Web.AppCode.Session
{
public interface ISessionContext
{
T Get<T>(string key) where T : class;
void Set<T>(string key, T value) where T : class;
void Remove(string key);
void Clear();
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace SIG.Web.AppCode.Session
{
[Serializable]
public sealed class MenuItemModel
{
public int CveModulo { get; set; }
public int? CveModuloPadre { get; set; }
public string NombreModulo { get; set; }
public string Url { get; set; }
public string ClaseCssIcono { get; set; }
public string ClaseCssEstilo { get; set; }
public int OrdenVisualizacion { get; set; }
public bool Visible { get; set; } = true;
public List<MenuItemModel> Hijos { get; set; } = new List<MenuItemModel>();
}
}

View File

@@ -0,0 +1,8 @@
namespace SIG.Web.AppCode.Session
{
public static class SessionKeys
{
public const string CurrentUser = "SIG_CurrentUser";
public const string TokenSesion = "SIG_SessionToken";
}
}

View File

@@ -0,0 +1,16 @@
namespace SIG.Web.AppCode.Session
{
public sealed class UserSessionModel
{
public int CveUsuario { get; set; }
public int CveRol { get; set; }
public int CveEmpresa { get; set; }
public string NombreUsuario { get; set; }
public string Nombre { get; set; }
public string CorreoElectronico { get; set; }
public string NombreRol { get; set; }
public string NombreDominio { get; set; }
public string TokenSesion { get; set; }
public bool DebeCambiarContrasena { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
using System.Web;
namespace SIG.Web.AppCode.Session
{
public sealed class WebFormsSessionContext : ISessionContext
{
public T Get<T>(string key) where T : class
{
return HttpContext.Current?.Session?[key] as T;
}
public void Set<T>(string key, T value) where T : class
{
if (HttpContext.Current?.Session == null) return;
HttpContext.Current.Session[key] = value;
}
public void Remove(string key)
{
HttpContext.Current?.Session?.Remove(key);
}
public void Clear()
{
HttpContext.Current?.Session?.Clear();
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using Newtonsoft.Json;
using SIG.Web.AppCode.Configuration;
using SIG.Web.AppCode.Contracts.Search;
namespace SIG.Web.AppCode.SigControls
{
[ToolboxData("<{0}:BuscadorCatalogo runat=server></{0}:BuscadorCatalogo>")]
public class BuscadorCatalogo : CompositeControl, INamingContainer
{
private TextBox _textBox;
private HiddenField _value;
private Button _button;
[Category("SIG")]
public string Catalogo
{
get { return ViewState["Catalogo"] as string ?? string.Empty; }
set { ViewState["Catalogo"] = value; }
}
[Category("SIG")]
public string Text
{
get { EnsureChildControls(); return _textBox.Text; }
set { EnsureChildControls(); _textBox.Text = value; }
}
[Category("SIG")]
public string SelectedValue
{
get { EnsureChildControls(); return _value.Value; }
set { EnsureChildControls(); _value.Value = value; }
}
protected override void CreateChildControls()
{
Controls.Clear();
_textBox = new TextBox { ID = "Txt", CssClass = "form-control sig-buscador-input" };
_value = new HiddenField { ID = "Hdn" };
_button = new Button { ID = "Btn", Text = "...", CssClass = "btn btn-default sig-buscador-button" };
_button.OnClientClick = "return false;";
Controls.Add(_value);
Controls.Add(_textBox);
Controls.Add(_button);
}
protected override void Render(HtmlTextWriter writer)
{
EnsureChildControls();
writer.AddAttribute(HtmlTextWriterAttribute.Class, "input-group sig-buscador-catalogo");
writer.AddAttribute("data-catalogo", Catalogo ?? string.Empty);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
_textBox.RenderControl(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Class, "input-group-btn");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
_button.RenderControl(writer);
writer.RenderEndTag();
_value.RenderControl(writer);
writer.RenderEndTag();
}
public static string SearchJson(string json)
{
var request = JsonConvert.DeserializeObject<BuscadorCatalogoCallbackSolicitud>(json ?? "{}") ?? new BuscadorCatalogoCallbackSolicitud();
var user = AppServices.UserContext.GetCurrentUser();
var response = AppServices.SearchProvider.Search(request, user);
return JsonConvert.SerializeObject(response);
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SIG.Web.AppCode.SigControls
{
public sealed class DropdownGridColumn
{
public string DataField { get; set; }
public string HeaderText { get; set; }
public bool Visible { get; set; } = true;
}
[ToolboxData("<{0}:DropdownGrid runat=server></{0}:DropdownGrid>")]
public class DropdownGrid : CompositeControl, INamingContainer
{
private HiddenField _selectedValues;
private TextBox _text;
public List<DropdownGridColumn> Columns
{
get { return ViewState["Columns"] as List<DropdownGridColumn> ?? new List<DropdownGridColumn>(); }
set { ViewState["Columns"] = value; }
}
[Category("SIG")]
public bool Multiple
{
get { return ViewState["Multiple"] as bool? ?? false; }
set { ViewState["Multiple"] = value; }
}
[Category("SIG")]
public string SelectedValues
{
get { EnsureChildControls(); return _selectedValues.Value; }
set { EnsureChildControls(); _selectedValues.Value = value; }
}
[Category("SIG")]
public string Text
{
get { EnsureChildControls(); return _text.Text; }
set { EnsureChildControls(); _text.Text = value; }
}
protected override void CreateChildControls()
{
Controls.Clear();
_selectedValues = new HiddenField { ID = "HdnValues" };
_text = new TextBox { ID = "TxtText", CssClass = "form-control sig-dropdown-grid-text" };
Controls.Add(_selectedValues);
Controls.Add(_text);
}
protected override void Render(HtmlTextWriter writer)
{
EnsureChildControls();
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-dropdown-grid");
writer.AddAttribute("data-multiple", Multiple ? "true" : "false");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
_selectedValues.RenderControl(writer);
_text.RenderControl(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-dropdown-grid-popup");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.Write("<!-- grid popup rendered by JavaScript -->");
writer.RenderEndTag();
writer.RenderEndTag();
}
public IReadOnlyList<string> GetSelectedValueList()
{
return (SelectedValues ?? string.Empty)
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace SIG.Web.AppCode.SigControls
{
[Serializable]
public sealed class FiltroActivo
{
public string Nombre { get; set; }
public string Titulo { get; set; }
public string Descripcion { get; set; }
public int Orden { get; set; }
public bool Visible { get; set; } = true;
}
}

View File

@@ -0,0 +1,9 @@
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfClientAction
{
public string Action { get; set; }
public string Message { get; set; }
public string Url { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfDocumentComposer
{
public byte[] Compose(byte[] originalPdf)
{
return originalPdf ?? new byte[0];
}
}
}

View File

@@ -0,0 +1,9 @@
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfSignatureAsset
{
public string Name { get; set; }
public byte[] Bytes { get; set; }
public string ContentType { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfSignatureComposer
{
public byte[] ApplySignatures(byte[] originalPdf, IEnumerable<PdfSignaturePlacement> placements)
{
// Placeholder. Keep signature composition behind this class.
return originalPdf ?? new byte[0];
}
}
}

View File

@@ -0,0 +1,11 @@
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfSignaturePlacement
{
public int Page { get; set; }
public float X { get; set; }
public float Y { get; set; }
public float Width { get; set; }
public float Height { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfSignatureSavingEventArgs : EventArgs
{
public PdfSignatureAsset Signature { get; }
public bool Cancel { get; set; }
public PdfSignatureSavingEventArgs(PdfSignatureAsset signature)
{
Signature = signature;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfSignedDocumentEventArgs : EventArgs
{
public byte[] Bytes { get; }
public string FileName { get; }
public PdfSignedDocumentEventArgs(byte[] bytes, string fileName)
{
Bytes = bytes;
FileName = fileName;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public enum PdfSourceMode
{
None,
Bytes,
FilePath,
Url,
TempStorage
}
}

View File

@@ -0,0 +1,9 @@
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfStoredSignature
{
public int Id { get; set; }
public string Name { get; set; }
public string DataUrl { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.IO;
using System.Web;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public static class PdfTempStorage
{
private const string Folder = "~/App_Data/PdfTemp";
public static string Save(byte[] bytes, string extension = ".pdf")
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
var dir = HttpContext.Current.Server.MapPath(Folder);
Directory.CreateDirectory(dir);
var fileName = Guid.NewGuid().ToString("N") + extension;
var path = Path.Combine(dir, fileName);
File.WriteAllBytes(path, bytes);
return path;
}
public static byte[] Read(string path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
return null;
return File.ReadAllBytes(path);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfUploadedDocument
{
public string FileName { get; set; }
public byte[] Bytes { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using System.Web;
using System.Web.SessionState;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfViewerHandler : IHttpHandler, IRequiresSessionState
{
public bool IsReusable => false;
public void ProcessRequest(HttpContext context)
{
var path = context.Request.QueryString["path"];
var bytes = PdfTempStorage.Read(path);
if (bytes == null)
{
context.Response.StatusCode = 404;
return;
}
context.Response.ContentType = "application/pdf";
context.Response.BinaryWrite(bytes);
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
[Serializable]
public sealed class PdfViewerState
{
public string DocumentId { get; set; }
public PdfSourceMode SourceMode { get; set; }
public string FileName { get; set; }
public string TempPath { get; set; }
public List<PdfSignaturePlacement> Signatures { get; set; } = new List<PdfSignaturePlacement>();
}
}

View File

@@ -0,0 +1,35 @@
using System.IO;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.SessionState;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
public sealed class PdfViewerUploadHandler : IHttpHandler, IRequiresSessionState
{
public bool IsReusable => false;
public void ProcessRequest(HttpContext context)
{
if (context.Request.Files.Count == 0)
{
WriteJson(context, new { success = false, message = "No file received." });
return;
}
var file = context.Request.Files[0];
using (var ms = new MemoryStream())
{
file.InputStream.CopyTo(ms);
var path = PdfTempStorage.Save(ms.ToArray());
WriteJson(context, new { success = true, path, fileName = file.FileName });
}
}
private static void WriteJson(HttpContext context, object payload)
{
context.Response.ContentType = "application/json";
context.Response.Write(new JavaScriptSerializer().Serialize(payload));
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.ComponentModel;
using System.Net;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SIG.Web.AppCode.SigControls.PDFViewer
{
[ToolboxData("<{0}:SigPdfViewer runat=server></{0}:SigPdfViewer>")]
public class SigPdfViewer : CompositeControl, INamingContainer
{
private HiddenField _path;
[Category("SIG")]
public string DocumentPath
{
get { EnsureChildControls(); return _path.Value; }
set { EnsureChildControls(); _path.Value = value; }
}
[Category("SIG")]
public bool ShowSignaturePanel
{
get { return ViewState["ShowSignaturePanel"] as bool? ?? false; }
set { ViewState["ShowSignaturePanel"] = value; }
}
public void LoadFromBytes(byte[] pdfBytes, string fileName = "document.pdf")
{
DocumentPath = PdfTempStorage.Save(pdfBytes);
}
public void LoadFromServerPath(string physicalPath)
{
DocumentPath = physicalPath;
}
public void LoadFromUrl(string url)
{
using (var client = new WebClient())
{
LoadFromBytes(client.DownloadData(url));
}
}
protected override void CreateChildControls()
{
Controls.Clear();
_path = new HiddenField { ID = "HdnPdfPath" };
Controls.Add(_path);
}
protected override void Render(HtmlTextWriter writer)
{
EnsureChildControls();
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-pdf-control");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
_path.RenderControl(writer);
if (string.IsNullOrWhiteSpace(DocumentPath))
{
writer.AddAttribute(HtmlTextWriterAttribute.Class, "alert alert-info");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.WriteEncodedText("No PDF loaded.");
writer.RenderEndTag();
}
else
{
var url = "SigPdfViewerHandler.ashx?path=" + Page.Server.UrlEncode(DocumentPath);
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-pdf-toolbar");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.AddAttribute(HtmlTextWriterAttribute.Href, url);
writer.AddAttribute(HtmlTextWriterAttribute.Target, "_blank");
writer.AddAttribute(HtmlTextWriterAttribute.Class, "btn btn-default");
writer.RenderBeginTag(HtmlTextWriterTag.A);
writer.Write("Open PDF");
writer.RenderEndTag();
writer.RenderEndTag();
writer.AddAttribute(HtmlTextWriterAttribute.Src, url);
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-pdf-frame");
writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "100%");
writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "700px");
writer.RenderBeginTag(HtmlTextWriterTag.Iframe);
writer.RenderEndTag();
}
writer.RenderEndTag();
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace SIG.Web.AppCode.SigControls
{
[ToolboxData("<{0}:PorcentajeBox runat=server></{0}:PorcentajeBox>")]
public class PorcentajeBox : WebControl, IPostBackDataHandler
{
[Category("SIG")]
public decimal Value
{
get { return ViewState["Value"] as decimal? ?? 0m; }
set { ViewState["Value"] = Math.Max(0m, Math.Min(100m, value)); }
}
[Category("SIG")]
public string HiddenFieldName => UniqueID + "$value";
protected override void RenderContents(HtmlTextWriter writer)
{
var value = Value.ToString("0.##", CultureInfo.InvariantCulture);
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-porcentaje-box");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden");
writer.AddAttribute(HtmlTextWriterAttribute.Name, HiddenFieldName);
writer.AddAttribute(HtmlTextWriterAttribute.Value, value);
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-porcentaje-track");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.AddStyleAttribute(HtmlTextWriterStyle.Width, value + "%");
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-porcentaje-fill");
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.RenderEndTag();
writer.RenderEndTag();
writer.AddAttribute(HtmlTextWriterAttribute.Class, "sig-porcentaje-text");
writer.RenderBeginTag(HtmlTextWriterTag.Span);
writer.WriteEncodedText(value + "%");
writer.RenderEndTag();
writer.RenderEndTag();
}
public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
var posted = postCollection[HiddenFieldName];
decimal newValue;
if (!decimal.TryParse(posted, NumberStyles.Any, CultureInfo.InvariantCulture, out newValue))
return false;
if (newValue == Value) return false;
Value = newValue;
return true;
}
public void RaisePostDataChangedEvent()
{
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace SIG.Web.AppCode.Utilities
{
public static partial class DataReaderExtensions
{
public static async Task<T> ToObjectAsync<T>(
this DbDataReader reader,
CancellationToken cancellationToken = default(CancellationToken))
where T : class, new()
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
bool hasRow = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
return hasRow ? MapCurrentRow<T>(reader) : null;
}
public static async Task<List<T>> ToListAsync<T>(
this DbDataReader reader,
CancellationToken cancellationToken = default(CancellationToken))
where T : class, new()
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
var result = new List<T>();
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
result.Add(MapCurrentRow<T>(reader));
}
return result;
}
public static async Task<DataTable> ToDataTableAsync(
this DbDataReader reader,
string tableName = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
var result = string.IsNullOrWhiteSpace(tableName)
? new DataTable()
: new DataTable(tableName);
result.Locale = CultureInfo.InvariantCulture;
for (int index = 0; index < reader.FieldCount; index++)
{
string columnName = reader.GetName(index);
Type columnType = reader.GetFieldType(index) ?? typeof(object);
if (result.Columns.Contains(columnName))
{
throw new InvalidOperationException(
"El resultado contiene una columna duplicada: " + columnName + ".");
}
result.Columns.Add(columnName, columnType);
}
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
var values = new object[reader.FieldCount];
reader.GetValues(values);
result.Rows.Add(values);
}
return result;
}
public static async Task<JArray> ToJsonArrayAsync(
this DbDataReader reader,
CancellationToken cancellationToken = default(CancellationToken))
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
var result = new JArray();
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
var item = new JObject();
for (int index = 0; index < reader.FieldCount; index++)
{
string columnName = reader.GetName(index);
object value = reader.GetValue(index);
item[columnName] = value == null || value == DBNull.Value
? JValue.CreateNull()
: JToken.FromObject(value);
}
result.Add(item);
}
return result;
}
}
}

View File

@@ -0,0 +1,198 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq;
namespace SIG.Web.AppCode.Utilities
{
public static partial class DataReaderExtensions
{
private static readonly ConcurrentDictionary<Type, PropertyMap[]> PropertyMaps =
new ConcurrentDictionary<Type, PropertyMap[]>();
public static T ToObject<T>(this IDataReader reader) where T : class, new()
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
return reader.Read() ? MapCurrentRow<T>(reader) : null;
}
public static List<T> ToList<T>(this IDataReader reader) where T : class, new()
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
var result = new List<T>();
while (reader.Read())
{
result.Add(MapCurrentRow<T>(reader));
}
return result;
}
public static DataTable ToDataTable(this IDataReader reader, string tableName = null)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
var result = string.IsNullOrWhiteSpace(tableName)
? new DataTable()
: new DataTable(tableName);
result.Locale = CultureInfo.InvariantCulture;
result.Load(reader);
return result;
}
public static JArray ToJsonArray(this IDataReader reader)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
var result = new JArray();
while (reader.Read())
{
var item = new JObject();
for (int index = 0; index < reader.FieldCount; index++)
{
string columnName = reader.GetName(index);
object value = reader.GetValue(index);
item[columnName] = value == null || value == DBNull.Value
? JValue.CreateNull()
: JToken.FromObject(value);
}
result.Add(item);
}
return result;
}
private static T MapCurrentRow<T>(IDataRecord record) where T : class, new()
{
T item = new T();
IDictionary<string, int> columns = GetColumns(record);
foreach (PropertyMap map in GetPropertyMaps(typeof(T)))
{
int ordinal;
if (!columns.TryGetValue(map.ColumnName, out ordinal) || record.IsDBNull(ordinal))
{
continue;
}
object convertedValue = ConvertValue(record.GetValue(ordinal), map.Property.PropertyType);
map.Property.SetValue(item, convertedValue, null);
}
return item;
}
private static IDictionary<string, int> GetColumns(IDataRecord record)
{
var columns = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
for (int index = 0; index < record.FieldCount; index++)
{
string name = record.GetName(index);
if (!columns.ContainsKey(name))
{
columns.Add(name, index);
}
}
return columns;
}
private static PropertyMap[] GetPropertyMaps(Type type)
{
return PropertyMaps.GetOrAdd(type, currentType =>
currentType
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(property => property.CanWrite && property.GetIndexParameters().Length == 0)
.Select(property =>
{
ColumnAttribute column = property.GetCustomAttributes(typeof(ColumnAttribute), true)
.OfType<ColumnAttribute>()
.FirstOrDefault();
return new PropertyMap
{
ColumnName = column == null || string.IsNullOrWhiteSpace(column.Name)
? property.Name
: column.Name,
Property = property
};
})
.ToArray());
}
private static object ConvertValue(object value, Type destinationType)
{
Type targetType = Nullable.GetUnderlyingType(destinationType) ?? destinationType;
if (targetType.IsInstanceOfType(value))
{
return value;
}
if (targetType.IsEnum)
{
return value is string
? Enum.Parse(targetType, Convert.ToString(value, CultureInfo.InvariantCulture), true)
: Enum.ToObject(targetType, value);
}
if (targetType == typeof(Guid))
{
return value is Guid ? value : Guid.Parse(Convert.ToString(value, CultureInfo.InvariantCulture));
}
if (targetType == typeof(TimeSpan))
{
return value is TimeSpan ? value : TimeSpan.Parse(Convert.ToString(value, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
}
if (targetType == typeof(DateTimeOffset))
{
return value is DateTimeOffset
? value
: DateTimeOffset.Parse(Convert.ToString(value, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
}
if (targetType == typeof(bool) && value is string)
{
string booleanValue = Convert.ToString(value, CultureInfo.InvariantCulture);
return booleanValue == "1" || booleanValue.Equals("true", StringComparison.OrdinalIgnoreCase);
}
return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
}
private sealed class PropertyMap
{
public string ColumnName { get; set; }
public PropertyInfo Property { get; set; }
}
}
}

View File

@@ -0,0 +1,93 @@
%PDF-1.4
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
1 0 obj
<<
/F1 2 0 R /F2 3 0 R
>>
endobj
2 0 obj
<<
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
>>
endobj
3 0 obj
<<
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
>>
endobj
4 0 obj
<<
/Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
5 0 obj
<<
/Contents 10 0 R /MediaBox [ 0 0 612 792 ] /Parent 8 0 R /Resources <<
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>> /Rotate 0 /Trans <<
>>
/Type /Page
>>
endobj
6 0 obj
<<
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
>>
endobj
7 0 obj
<<
/Author (\(anonymous\)) /CreationDate (D:20260706205140+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260706205140+00'00') /Producer (ReportLab PDF Library - \(opensource\))
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
>>
endobj
8 0 obj
<<
/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages
>>
endobj
9 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 955
>>
stream
Gat%!gN)"%&:N^lp+agB@L6B`dp+r_a`N^([*Jd]m#5a@[Xc`.mn1#d6\%qP",PKBFa!ML2$FL4C;1Q2^t0:'mO:s:kn9pjHq5@;/fhWbB^Zfm&jo&3_XIQZ4_nt:,<SR_U>`Wq,;GjrM8Yd<'*X&D%!U///fns"iFa=BU4HOV=I5c1>SjDl$-[Ws9*+8H"k$IC04?UuMK#0V3Ihej4f$CciBP-+*\HOAft8ojM_m(jR'3PaM@?d3,G8o0U-U<VU=GoX!AiO;?B4"+q-a\S^2&W1QdCV%KA"d[k@<FM"q+RAKZUO8jM\Z)Z3=7=,Y^kAM@eXqn&3mTjQe0`nr60G!DQh5KR$G]:OrX?LasTfa<Ei&'r]?-.<[0I1"q;7cj3`[@OUh-#LEG25a'KT]F[UHZ+s0)2tj*TF%>_cZQTGE^Cel^A44UB[b!Ghf:]`1:c^K$]pPdAZOidR[2;kdi(CZ_lfQuPjh,AHh^QW7WN&uX*8<X_0Vm2AKasfp;2<*<jW'*5nJ]K0]<nK&@GX?$7E<+;%UiI?M>(1V6KX82YAtkW)c^T_)U^%kUX:-MOP2dXZfdGB?5Sp>[TNL;23Qg$%(r_ho%[pPWm6p?f6.n#mu.fQhE)1XLe]9d+6O4-r[G:M=&dA06LO2oEau+WEe_,dC28Al>[Ti9V)0`gbYV-6^a(i3N>3d@\#e1b2!<5-#OYI+c[oe!hMp0\BNpQ*Z]VjSW!:KF9.^n)&9F,EQ:PlE7VprSm*RY5LrsE+4+:L+,jO#Xia05OrhCW!IiFLM[NE@<HW?H@KUK7J[I2e^JX>+UG2DH]_iAMF7o;?(/tE`Rm&[^=<YVO>ij,oEfkdgu$[:X[Z+9"hSE#'_S=ej.kSSe`Ke<>%HeE)/jID]HW;*5lTq'0m>:CQLbG%p_buXtT7:t\*UU9\J9$>_mZ9?b#c/@FaLN=A'IfRU^fh2~>endstream
endobj
10 0 obj
<<
/Filter [ /ASCII85Decode /FlateDecode ] /Length 401
>>
stream
GasIa9i&Vk&A@g>]H\>oC]fuL1s1Z-+4^8:*U"-I>BdO^r-F-M(_JTg9JTsnhKo;e%ajF5!q()6Ns%k&Hj\8^qoHg&aJ8K#lo,/@4@C[I38ju-pY*?N7=6QpBREl9;?AT.+sk>H7q?<bL4(-+$Cqt_XAPqXN%U4<O^Fj[@tWZ&E?)[+anDof:X\TJ;TXq5bNlS,>#Lp(e/nUY%$ouH<0Q3>7P=@-#&OgV5q!%.j+FK'GX%)K*_T3LjPhg!B1NQ=F/5"L8;mI=f'H`'FBHFXV2Z[C$Y<a_!3L#%$&rrcK@<73`UY"L_Gi1E<JAl=3_UI*:lshhC=d5iG^UX.G':8WaQpL\KiTtU%P+Vr\q8NQcE[bQFaDM_`?j:'D;tu?`&:(Va-Ie$K]j)B@&qf~>endstream
endobj
xref
0 11
0000000000 65535 f
0000000061 00000 n
0000000102 00000 n
0000000209 00000 n
0000000321 00000 n
0000000514 00000 n
0000000708 00000 n
0000000776 00000 n
0000001056 00000 n
0000001121 00000 n
0000002166 00000 n
trailer
<<
/ID
[<11cd4642e6f5272b0b781904ee9674eb><11cd4642e6f5272b0b781904ee9674eb>]
% ReportLab generated PDF document -- digest (opensource)
/Info 7 0 R
/Root 6 0 R
/Size 11
>>
startxref
2658
%%EOF

View File

@@ -0,0 +1 @@
Copy files here.

1
SIG.Web/Global.asax Normal file
View File

@@ -0,0 +1 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="SIG.Web.Global" Language="C#" %>

22
SIG.Web/Global.asax.cs Normal file
View File

@@ -0,0 +1,22 @@
using System;
using System.Web;
using System.Web.Optimization;
using SIG.Web.AppCode.Configuration;
namespace SIG.Web
{
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
AppServices.Configure();
BundleConfig.RegisterBundles(BundleTable.Bundles);
ErrorHandlerConfig.Register();
}
protected void Application_Error(object sender, EventArgs e)
{
ErrorHandlerConfig.HandleServerError(Server.GetLastError(), Context);
}
}
}

View File

@@ -0,0 +1,16 @@
Archivos sugeridos para la carpeta ~/Imagenes/
- login-fondo-cerro-campana.jpg
Fondo sugerido para el login.
- logo-agua-hermosillo-blanco.png
Logo blanco con fondo transparente (extraido del mockup).
- icono-desarrolladores-azul.png
Icono de casa/desarrolladores azul con fondo transparente (extraido del mockup).
Archivos extra de referencia:
- login-preview-completo.png
- cerro-campana-referencia.jpg
- logo-agua-hermosillo-blanco_recorte.png
- icono-desarrolladores-azul_recorte.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Some files were not shown because too many files have changed in this diff Show More