
Creación de un Panel de administración de operaciones en MQL5 (Parte IV): Capa de seguridad de inicio de sesión
Tabla de contenidos:
- Introducción
- Descripción general de la seguridad en MQL5
- Breve resumen de la Parte III
- Integración de la protección con contraseña en el Panel de administración
- Pruebas y resultados
- Conclusión
Introducción
La seguridad es primordial en cualquier campo de especialización y no podemos permitirnos pasar por alto su importancia. Ante la amenaza persistente del acceso no autorizado, es crucial proteger nuestro Panel de administración de posibles intrusos. Si personas no autorizadas obtienen acceso, podrían manipular fácilmente el panel y poner en peligro nuestros esfuerzos de comunicación con la comunidad de radiodifusión. El objetivo principal de este sistema es facilitar una comunicación confiable y, si bien podemos mejorar la funcionalidad a nivel de Asesor Experto, los riesgos de intrusión siguen siendo significativos.
Un atacante que acceda al panel de control podría enviar mensajes engañosos a nuestros usuarios, causando confusión y dañando la reputación del administrador del sistema. Para mitigar estos riesgos, creo que es esencial implementar una capa de seguridad que restrinja el acceso a funciones clave sin las credenciales correctas. Este enfoque sencillo de la seguridad protege nuestro panel y también ayuda a mantener la integridad de nuestras comunicaciones y la confianza de nuestra comunidad.
Panel de inicio de sesión
Descripción general de la seguridad en MQL5
MQL5 ofrece una amplia gama de funciones de seguridad diseñadas para proteger tanto el código fuente como los archivos compilados (EX5), salvaguardando la propiedad intelectual y evitando el uso no autorizado. Los mecanismos clave incluyen el cifrado de archivos compilados, licencias basadas en cuentas y en tiempo e integración con DLL externas para protección adicional. La plataforma admite firmas digitales para verificar la autenticidad del código, mientras que MetaQuotes proporciona protección del código mediante compilación y ofuscación para disuadir la ingeniería inversa. Para los productos distribuidos a través del MQL5 Market, el cifrado adicional garantiza que solo los usuarios con licencia puedan acceder y utilizar el software, lo que establece un marco de seguridad sólido para los desarrolladores.
En 2012, Investeo, autor de MQL5, analizó diversos métodos para proteger los programas y el código MQL5, y compartió valiosos conocimientos sobre la implementación de técnicas como la protección con contraseña, la generación de claves, las licencias de cuenta única, la protección con límite de tiempo, las licencias remotas, el cifrado seguro de licencias y los métodos avanzados contra la descompilación. Su trabajo sirve como referencia fundamental para mejorar la seguridad del programa.
Objetivo del debate:
Reconociendo la importancia de la seguridad, nuestro objetivo es debatir la implementación de la protección con contraseña para acceder a las funciones del Panel de administración. Profundizaremos en las técnicas utilizadas para lograr los resultados mostrados en la imagen anterior, centrándonos en cómo podemos proteger el acceso de los usuarios de manera eficaz.
Áreas de seguridad que requieren atención en nuestro panel de administración:
A medida que nuestro programa evoluciona e incorpora nuevas funciones, reconocemos la creciente complejidad, especialmente para los desarrolladores novatos. Identificamos varias áreas clave de interés en materia de seguridad:
- Acceso seguro al panel de administración:
Para garantizar que ningún usuario no autorizado pueda acceder al Panel de administración sin la contraseña correcta, implementamos protección con contraseña. El acceso no autorizado podría dar lugar a la difusión de mensajes no deseados a la comunidad de operadores que confían en la información proporcionada por los administradores. Es posible que se produzcan clics aleatorios en los botones rápidos sin intención real, por lo que es esencial disponer de un código de acceso seguro. Aunque muchas aplicaciones utilizan la autenticación de dos factores (2FA) para una verificación adicional, nuestro enfoque actual sigue centrado en implementar funciones de seguridad básicas, con planes de incorporar opciones más avanzadas a medida que avancemos.
- Seguridad de los mensajes de la API de Telegram:
También priorizamos la seguridad de la comunicación a través de la API de Telegram introduciendo de forma segura el ID del chat y el token del bot durante el inicio del programa. Este enfoque garantiza que los datos confidenciales permanezcan protegidos en manos del usuario. Telegram emplea sólidas funciones de seguridad para proteger las comunicaciones de los usuarios, incluyendo la seguridad de la capa de transporte a través del protocolo MTProto para los chats estándar y el cifrado de extremo a extremo para los chats secretos. Además, Telegram admite 2FA, lo que permite a los usuarios administrar sesiones activas y mejorar la seguridad de la cuenta. Si bien los protocolos de seguridad de Telegram son sólidos, los usuarios también deben asegurarse de que sus dispositivos sean seguros, ya que los dispositivos comprometidos pueden socavar estas protecciones.
Breve resumen de la Parte III
En nuestra discusión anterior, abordamos la incorporación de métodos para la gestión de temas. Sin embargo, estábamos trabajando con archivos que están sujetos a cambios durante las actualizaciones de la plataforma MetaTrader 5. Cada vez que se lanza una actualización, se descarga e instala automáticamente al reiniciar. A continuación se muestra un fragmento de código que ilustra los errores que encontré cuando intenté compilarlo después de las actualizaciones.
'UpdateThemeColors' - undeclared identifier Admin Panel .mq5 390 16 'darkTheme' - some operator expected Admin Panel .mq5 390 34 'SetTextColor' - undeclared identifier Admin Panel .mq5 397 14 'textColor' - some operator expected Admin Panel .mq5 397 27 'SetBackgroundColor' - undeclared identifier Admin Panel .mq5 398 14 'bgColor' - some operator expected Admin Panel .mq5 398 33 'SetBorderColor' - undeclared identifier Admin Panel .mq5 399 14 'borderColor' - some operator expected Admin Panel .mq5 399 29 'SetTextColor' - undeclared identifier Admin Panel .mq5 424 12 'textColor' - some operator expected Admin Panel .mq5 424 25 'SetBackgroundColor' - undeclared identifier Admin Panel .mq5 425 12 'bgColor' - some operator expected Admin Panel .mq5 425 31 'SetBorderColor' - undeclared identifier Admin Panel .mq5 426 12 'borderColor' - some operator expected Admin Panel .mq5 426 27 14 errors, 1 warnings 15 2
Solución temporal
Para resolver el problema, es esencial comprender primero el origen del problema. Como se explicó anteriormente, las actualizaciones de la plataforma restablecen las bibliotecas que estábamos usando a sus estados predeterminados. En consecuencia, los métodos que implementamos para la gestión de temas ya no son válidos, por lo que ahora estamos encontrando errores. Para solucionar esto, debemos sobrescribir los archivos actualizados (Dialog.mqh, Edit.mqh y Button.mqh) con las versiones ampliadas que adjunté en el artículo anterior. Puede ubicar la carpeta para los archivos incluidos como se muestra en la imagen a continuación.
Cómo localizar fácilmente la carpeta raíz de dialog.mqh
Solución permanente:
Podemos cambiar el nombre del archivo Dialog.mqh y otros archivos relacionados que utilicemos a Extended_Dialog.mqh y ajustar nuestro código en consecuencia, pero asegúrese de actualizar cualquier instrucción #include que haga referencia al nombre antiguo del archivo para reflejar el nuevo nombre. Además, debemos verificar si hay otras dependencias que puedan hacer referencia a él y actualizarlas según sea necesario. Después de realizar estos cambios, volvemos a compilar nuestro proyecto para identificar si hay errores potenciales y probamos exhaustivamente la funcionalidad para garantizar que todo funcione correctamente. Esto lo guardará por separado con el nuevo nombre pero conservará el archivo original.
Por ejemplo, si ya hemos guardado el archivo como Extended_Dialog.mqh, podemos navegar hasta nuestro Panel de administración y ajustar el código de la siguiente manera:
#include <Controls\Extended_Dialog.mqh> #include <Controls\Extended_Button.mqh> #include <Controls\Extended_Edit.mqh> #include <Controls\Label.mqh>
Ventajas de ahorrar con un nombre diferente
Ofrece la posibilidad de adaptar la funcionalidad específicamente a sus necesidades añadiendo o ajustando características que no están presentes en la versión integrada. Esta personalización le permite crear una interfaz única que se adapta a sus necesidades. Además, el uso de nombres de archivo personalizados ayuda a evitar conflictos con bibliotecas integradas o bibliotecas de terceros, lo que reduce el riesgo de comportamientos inesperados debido a la superposición de nombres. Aislar sus mejoras en un archivo renombrado protege aún más sus personalizaciones para que no se vean afectadas por otras funciones integradas que podrían utilizar el cuadro de diálogo original, lo que garantiza que pueda desarrollar y mantener su proyecto sin interferencias de cambios externos.
Integración de la protección con contraseña en el panel de administración
En este proyecto, implementaremos una protección con contraseña condicional utilizando una contraseña de tipo cadena que puede incluir tanto letras como números, lo que aumenta la complejidad. Aunque un PIN de cuatro dígitos puede parecer simple, sigue siendo difícil de adivinar. En el panel de administración, utilizamos la clase Dialog para solicitar al usuario una contraseña al iniciar sesión, con condiciones establecidas para mostrar las funciones principales del panel solo después de introducir correctamente la contraseña.
A medida que continuamos desarrollando el programa del Panel de administración, nuestro objetivo principal es establecer una seguridad de inicio de sesión sólida para garantizar que solo los usuarios autorizados puedan acceder a las funciones administrativas confidenciales. Hemos reconocido la necesidad crítica de proteger nuestro sistema contra el acceso no autorizado y debatimos cómo implementar MQL5 para proteger nuestros productos.
Mecanismo de autenticación
Para proteger el panel de administración, implementamos un mecanismo de autenticación sencillo basado en contraseña que solicita a los usuarios una contraseña antes de concederles acceso a cualquier funcionalidad. Esta decisión refleja nuestro compromiso con la validación de la identidad del usuario como requisito previo para acceder a los componentes críticos del programa.
// Show authentication input dialog bool ShowAuthenticationPrompt() { if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create authentication dialog"); return false; } }
En la función ShowAuthenticationPrompt, diseñamos una interfaz fácil de usar que guía eficazmente a nuestros usuarios a través del proceso de autenticación. Al crear un cuadro de diálogo específico para la introducción de la contraseña, garantizamos que el punto de acceso principal al panel de administración siga siendo seguro y, al mismo tiempo, intuitivo.
Para facilitar la comprensión, he resumido el código para la creación de diálogos en el fragmento de código que aparece a continuación, junto con comentarios que explican cómo funciona. Si desea refrescar su memoria sobre ejes y coordenadas, consulte (Parte I).
// This condition checks if the authentication object is created successfully if (!authentication.Create( // Function call to create authentication ChartID(), // Retrieve the ID of the current chart "Authentication", // Label of the dialog window in this case it is 'Authentication' 0, // Initial X position on the chart also X_1 100, // Initial Y position on the chart also Y_1 500, // Width of the authentication window also X_2 300 // Height of the authentication window also Y_2 ))
Una vez establecido el cuadro de diálogo de autenticación, procedemos a organizar otros elementos de la interfaz de usuario de manera similar, aunque con valores diferentes. El proceso comienza con la creación de un cuadro de entrada de contraseña donde los usuarios pueden escribir sus credenciales, seguido de botones esenciales. En concreto, nos centramos en dos botones principales: el botón «Login» y el botón «Close». El botón «Login» se utiliza para enviar la contraseña introducida, mientras que el botón «Close» ofrece a los usuarios la opción de salir del cuadro de diálogo si no conocen la contraseña. A continuación se muestra un fragmento de código que ilustra la lógica para crear estos botones y la etiqueta de solicitud de contraseña.
// Create password input if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", 0, 20, 70, 260, 95)) { Print("Failed to create password input box"); return false; } authentication.Add(passwordInputBox); // Create prompt label if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", 0, 20, 20, 260, 20)) { Print("Failed to create password prompt label"); return false; } passwordPromptLabel.Text("Enter password: Access Admin Panel"); authentication.Add(passwordPromptLabel); // Create login button if (!loginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140)) { Print("Failed to create login button"); return false; } loginButton.Text("Login"); authentication.Add(loginButton); // Create close button for authentication if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) // Adjusted position { Print("Failed to create close button for authentication"); return false; } closeAuthButton.Text("Close"); authentication.Add(closeAuthButton); authentication.Show(); // Show the authentication dialog ChartRedraw(); // Redraw the chart to reflect changes return true; // Prompt shown successfully }
Gestión de contraseñas
Actualmente, utilizamos una contraseña simple codificada para las pruebas iniciales, lo que nos permite crear prototipos de la funcionalidad rápidamente. Sin embargo, entendemos plenamente que este enfoque conlleva riesgos, como la vulnerabilidad a ataques de fuerza bruta si el código se ve comprometido.
// Default password for authentication string Password = "2024";
Si bien reconocemos que el uso de una contraseña codificada agiliza nuestro desarrollo, necesitamos realizar una transición hacia una solución más segura en futuras actualizaciones, concretamente, implementar archivos de configuración cifrados o utilizar un sistema de gestión de cuentas de usuario más sofisticado para mejorar la seguridad.
Gestión de entradas del usuario
Para reforzar la seguridad, debemos asegurarnos de que el campo de introducción de la contraseña esté claramente definido en el cuadro de diálogo de autenticación. Al guiar a los usuarios para que introduzcan sus contraseñas y validar esos datos con la contraseña almacenada, nuestro objetivo es ofrecer una experiencia de inicio de sesión fluida y segura.// Handle login button click void OnLoginButtonClick() { string enteredPassword = passwordInputBox.Text(); if (enteredPassword == Password) // Check the entered password { authentication.Destroy(); // Hide the authentication dialog Print("Authentication successful."); adminPanel.Show(); // Show the admin panel after successful authentication } else { Print("Incorrect password. Please try again."); passwordInputBox.Text(""); // Clear the password input } }
En la función OnLoginButtonClick, el programa comprueba si la contraseña introducida coincide con la contraseña almacenada. Tras completar con éxito el inicio de sesión, se ocultó el cuadro de diálogo de autenticación y se mostró el panel de administración al usuario. Si la contraseña es incorrecta, se borra el campo de entrada y se solicita al usuario que vuelva a intentarlo, lo que garantiza que comprenda claramente el proceso y se sienta seguro durante el inicio de sesión.
También tenemos un controlador para el botón «Close», que se encarga de la lógica de salida. Al hacer clic en este botón, se cierra el cuadro de diálogo de autenticación y se elimina por completo al experto del gráfico, lo que garantiza que no quede ningún acceso a las funciones de administración. Esta medida refuerza la seguridad y proporciona una vía de salida clara para los usuarios que decidan no continuar con el proceso de autenticación. Así es como se define el controlador:
//+------------------------------------------------------------------+ //| Handle close button click for authentication | //+------------------------------------------------------------------+ void OnCloseAuthButtonClick() { authentication.Destroy(); ExpertRemove(); // Remove the expert if user closes the authentication dialog Print("Authentication dialog closed."); }
En este controlador, el método authentication. Destroy() cierra eficazmente el cuadro de diálogo, mientras que ExpertRemove() garantiza que el asesor experto se elimine por completo de la vista, lo que mejora la seguridad general de la aplicación.
Totalmente incorporado al programa principal:
//+------------------------------------------------------------------+ //| Admin Panel.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/en/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property version "1.19" #include <Trade\Trade.mqh> #include <Controls\Dialog.mqh> #include <Controls\Button.mqh> #include <Controls\Edit.mqh> #include <Controls\Label.mqh> // Input parameters input string QuickMessage1 = "Updates"; input string QuickMessage2 = "Close all"; input string QuickMessage3 = "In deep profits"; input string QuickMessage4 = "Hold position"; input string QuickMessage5 = "Swing Entry"; input string QuickMessage6 = "Scalp Entry"; input string QuickMessage7 = "Book profit"; input string QuickMessage8 = "Invalid Signal"; input string InputChatId = "Enter Chat ID from Telegram bot API"; input string InputBotToken = "Enter BOT TOKEN from your Telegram bot"; // Global variables CDialog adminPanel; CDialog authentication; // Renamed from passwordPanel CButton sendButton, clearButton, changeFontButton, toggleThemeButton, loginButton, closeAuthButton; CButton quickMessageButtons[8], minimizeButton, maximizeButton, closeButton; CEdit inputBox, passwordInputBox; CLabel charCounter, passwordPromptLabel; bool minimized = false; bool darkTheme = false; int MAX_MESSAGE_LENGTH = 4096; string availableFonts[] = { "Arial", "Courier New", "Verdana", "Times New Roman", "Britannic Bold", "Dubai Medium", "Impact", "Ink Tree", "Brush Script MT"}; int currentFontIndex = 0; // Default password for authentication string Password = "2024"; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if (!ShowAuthenticationPrompt()) { Print("Authorization failed. Exiting..."); return INIT_FAILED; // Exit if the authorization fails } // Initialize the main admin panel if (!adminPanel.Create(ChartID(), "Admin Panel", 0, 30, 30, 500, 500)) { Print("Failed to create admin panel dialog"); return INIT_FAILED; } // Create controls for the admin panel if (!CreateControls()) { Print("Control creation failed"); return INIT_FAILED; } // Initially hide the admin panel adminPanel.Hide(); Print("Initialization complete"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Show authentication input dialog | //+------------------------------------------------------------------+ bool ShowAuthenticationPrompt() { if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create authentication dialog"); return false; } // Create password input if (!passwordInputBox.Create(ChartID(), "PasswordInputBox", 0, 20, 70, 260, 95)) { Print("Failed to create password input box"); return false; } authentication.Add(passwordInputBox); // Create prompt label if (!passwordPromptLabel.Create(ChartID(), "PasswordPromptLabel", 0, 20, 20, 260, 20)) { Print("Failed to create password prompt label"); return false; } passwordPromptLabel.Text("Enter password: Access Admin Panel"); authentication.Add(passwordPromptLabel); // Create login button if (!loginButton.Create(ChartID(), "LoginButton", 0, 20, 120, 100, 140)) { Print("Failed to create login button"); return false; } loginButton.Text("Login"); authentication.Add(loginButton); // Create close button for authentication if (!closeAuthButton.Create(ChartID(), "CloseAuthButton", 0, 120, 120, 200, 140)) // Adjusted position { Print("Failed to create close button for authentication"); return false; } closeAuthButton.Text("Close"); authentication.Add(closeAuthButton); authentication.Show(); // Show the authentication dialog ChartRedraw(); // Redraw the chart to reflect changes return true; // Prompt shown successfully } //+------------------------------------------------------------------+ //| Handle chart events | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) { // Handle button clicks inside the authentication dialog if (sparam == "LoginButton") { OnLoginButtonClick(); // Call the login button handler } else if (sparam == "CloseAuthButton") // Made sure this matches the ID { OnCloseAuthButtonClick(); // Call the close button handler } } switch (id) { case CHARTEVENT_OBJECT_CLICK: if (sparam == "SendButton") OnSendButtonClick(); else if (sparam == "ClearButton") OnClearButtonClick(); else if (sparam == "ChangeFontButton") OnChangeFontButtonClick(); else if (sparam == "ToggleThemeButton") OnToggleThemeButtonClick(); else if (sparam == "MinimizeButton") OnMinimizeButtonClick(); else if (sparam == "MaximizeButton") OnMaximizeButtonClick(); else if (sparam == "CloseButton") OnCloseButtonClick(); else if (StringFind(sparam, "QuickMessageButton") != -1) { long index = StringToInteger(StringSubstr(sparam, 18)); OnQuickMessageButtonClick(index - 1); } break; case CHARTEVENT_OBJECT_ENDEDIT: if (sparam == "InputBox") OnInputChange(); break; } } //+------------------------------------------------------------------+ //| Handle login button click | //+------------------------------------------------------------------+ void OnLoginButtonClick() { string enteredPassword = passwordInputBox.Text(); if (enteredPassword == Password) // Check the entered password { authentication.Destroy(); // Hide the authentication dialog Print("Authentication successful."); adminPanel.Show(); // Show the admin panel after successful authentication } else { Print("Incorrect password. Please try again."); passwordInputBox.Text(""); // Clear the password input } } //+------------------------------------------------------------------+ //| Handle close button click for authentication | //+------------------------------------------------------------------+ void OnCloseAuthButtonClick() { authentication.Destroy(); ExpertRemove(); // Remove the expert if user closes the authentication dialog Print("Authentication dialog closed."); } //+------------------------------------------------------------------+ //| Create necessary UI controls | //+------------------------------------------------------------------+ bool CreateControls() { long chart_id = ChartID(); // Create the input box if (!inputBox.Create(chart_id, "InputBox", 0, 5, 25, 460, 95)) { Print("Failed to create input box"); return false; } adminPanel.Add(inputBox); // Character counter if (!charCounter.Create(chart_id, "CharCounter", 0, 380, 5, 460, 25)) { Print("Failed to create character counter"); return false; } charCounter.Text("0/" + IntegerToString(MAX_MESSAGE_LENGTH)); adminPanel.Add(charCounter); // Clear button if (!clearButton.Create(chart_id, "ClearButton", 0, 235, 95, 345, 125)) { Print("Failed to create clear button"); return false; } clearButton.Text("Clear"); adminPanel.Add(clearButton); // Send button if (!sendButton.Create(chart_id, "SendButton", 0, 350, 95, 460, 125)) { Print("Failed to create send button"); return false; } sendButton.Text("Send"); adminPanel.Add(sendButton); // Change font button if (!changeFontButton.Create(chart_id, "ChangeFontButton", 0, 95, 95, 230, 115)) { Print("Failed to create change font button"); return false; } changeFontButton.Text("Font<>"); adminPanel.Add(changeFontButton); // Toggle theme button if (!toggleThemeButton.Create(chart_id, "ToggleThemeButton", 0, 5, 95, 90, 115)) { Print("Failed to create toggle theme button"); return false; } toggleThemeButton.Text("Theme<>"); adminPanel.Add(toggleThemeButton); // Minimize button if (!minimizeButton.Create(chart_id, "MinimizeButton", 0, 375, -22, 405, 0)) // Adjusted Y-coordinate for visibility { Print("Failed to create minimize button"); return false; } minimizeButton.Text("_"); adminPanel.Add(minimizeButton); // Maximize button if (!maximizeButton.Create(chart_id, "MaximizeButton", 0, 405, -22, 435, 0)) // Adjusted Y-coordinate for visibility { Print("Failed to create maximize button"); return false; } maximizeButton.Text("[ ]"); adminPanel.Add(maximizeButton); // Close button for admin panel if (!closeButton.Create(chart_id, "CloseButton", 0, 435, -22, 465, 0)) // Adjusted Y-coordinate for visibility { Print("Failed to create close button"); return false; } closeButton.Text("X"); adminPanel.Add(closeButton); // Quick messages return CreateQuickMessageButtons(); } //+------------------------------------------------------------------+ //| Create quick message buttons | //+------------------------------------------------------------------+ bool CreateQuickMessageButtons() { string quickMessages[8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; int startX = 5, startY = 160, width = 222, height = 65, spacing = 5; for (int i = 0; i < 8; i++) { if (!quickMessageButtons[i].Create(ChartID(), "QuickMessageButton" + IntegerToString(i + 1), 0, startX + (i % 2) * (width + spacing), startY + (i / 2) * (height + spacing), startX + (i % 2) * (width + spacing) + width, startY + (i / 2) * (height + spacing) + height)) { Print("Failed to create quick message button ", i + 1); return false; } quickMessageButtons[i].Text(quickMessages[i]); adminPanel.Add(quickMessageButtons[i]); } return true; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { adminPanel.Destroy(); Print("Deinitialization complete"); } //+------------------------------------------------------------------+ //| Handle custom message send button click | //+------------------------------------------------------------------+ void OnSendButtonClick() { string message = inputBox.Text(); if (message != "") { if (SendMessageToTelegram(message)) Print("Custom message sent: ", message); else Print("Failed to send custom message."); } else { Print("No message entered."); } } //+------------------------------------------------------------------+ //| Handle clear button click | //+------------------------------------------------------------------+ void OnClearButtonClick() { inputBox.Text(""); OnInputChange(); Print("Input box cleared."); } //+------------------------------------------------------------------+ //| Handle quick message button click | //+------------------------------------------------------------------+ void OnQuickMessageButtonClick(int index) { string quickMessages[8] = { QuickMessage1, QuickMessage2, QuickMessage3, QuickMessage4, QuickMessage5, QuickMessage6, QuickMessage7, QuickMessage8 }; string message = quickMessages[index]; if (SendMessageToTelegram(message)) Print("Quick message sent: ", message); else Print("Failed to send quick message."); } //+------------------------------------------------------------------+ //| Update character counter | //+------------------------------------------------------------------+ void OnInputChange() { int currentLength = StringLen(inputBox.Text()); charCounter.Text(IntegerToString(currentLength) + "/" + IntegerToString(MAX_MESSAGE_LENGTH)); ChartRedraw(); } //+------------------------------------------------------------------+ //| Handle toggle theme button click | //+------------------------------------------------------------------+ void OnToggleThemeButtonClick() { darkTheme = !darkTheme; UpdateThemeColors(); Print("Theme toggled: ", darkTheme ? "Dark" : "Light"); } //+------------------------------------------------------------------+ //| Update theme colors for the panel | //+------------------------------------------------------------------+ void UpdateThemeColors() { // Use the dialog's theme update method as a placeholder. adminPanel.UpdateThemeColors(darkTheme); color textColor = darkTheme ? clrWhite : clrBlack; color buttonBgColor = darkTheme ? clrDarkSlateGray : clrGainsboro; color borderColor = darkTheme ? clrSlateGray : clrGray; color bgColor = darkTheme ? clrDarkBlue : clrWhite; inputBox.SetTextColor(textColor); inputBox.SetBackgroundColor(bgColor); inputBox.SetBorderColor(borderColor); UpdateButtonTheme(clearButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(sendButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(toggleThemeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(changeFontButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(minimizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(maximizeButton, textColor, buttonBgColor, borderColor); UpdateButtonTheme(closeButton, textColor, buttonBgColor, borderColor); for (int i = 0; i < ArraySize(quickMessageButtons); i++) { UpdateButtonTheme(quickMessageButtons[i], textColor, buttonBgColor, borderColor); } charCounter.Color(textColor); ChartRedraw(); } //+------------------------------------------------------------------+ //| Apply theme settings to a button | //+------------------------------------------------------------------+ void UpdateButtonTheme(CButton &button, color textColor, color bgColor, color borderColor) { button.SetTextColor(textColor); button.SetBackgroundColor(bgColor); button.SetBorderColor(borderColor); } //+------------------------------------------------------------------+ //| Handle change font button click | //+------------------------------------------------------------------+ void OnChangeFontButtonClick() { currentFontIndex = (currentFontIndex + 1) % ArraySize(availableFonts); inputBox.Font(availableFonts[currentFontIndex]); clearButton.Font(availableFonts[currentFontIndex]); sendButton.Font(availableFonts[currentFontIndex]); toggleThemeButton.Font(availableFonts[currentFontIndex]); changeFontButton.Font(availableFonts[currentFontIndex]); for (int i = 0; i < ArraySize(quickMessageButtons); i++) { quickMessageButtons[i].Font(availableFonts[currentFontIndex]); } Print("Font changed to: ", availableFonts[currentFontIndex]); ChartRedraw(); } //+------------------------------------------------------------------+ //| Handle minimize button click | //+------------------------------------------------------------------+ void OnMinimizeButtonClick() { minimized = true; adminPanel.Hide(); minimizeButton.Hide(); maximizeButton.Show(); closeButton.Show(); Print("Panel minimized."); } //+------------------------------------------------------------------+ //| Handle maximize button click | //+------------------------------------------------------------------+ void OnMaximizeButtonClick() { if (minimized) { adminPanel.Show(); minimizeButton.Show(); maximizeButton.Hide(); closeButton.Hide(); Print("Panel maximized."); } } //+------------------------------------------------------------------+ //| Handle close button click for admin panel | //+------------------------------------------------------------------+ void OnCloseButtonClick() { ExpertRemove(); Print("Admin Panel closed."); } //+------------------------------------------------------------------+ //| Send the message to Telegram | //+------------------------------------------------------------------+ bool SendMessageToTelegram(string message) { string url = "https://api.telegram.org/bot" + InputBotToken + "/sendMessage"; string jsonMessage = "{\"chat_id\":\"" + InputChatId + "\", \"text\":\"" + message + "\"}"; char post_data[]; ArrayResize(post_data, StringToCharArray(jsonMessage, post_data, 0, WHOLE_ARRAY) - 1); int timeout = 5000; char result[]; string responseHeaders; int res = WebRequest("POST", url, "Content-Type: application/json\r\n", timeout, post_data, result, responseHeaders); if (res == 200) { Print("Message sent successfully: ", message); return true; } else { Print("Failed to send message. HTTP code: ", res, " Error code: ", GetLastError()); Print("Response: ", CharArrayToString(result)); return false; } }
Pruebas y resultados
Nuestro código se compiló correctamente y, al iniciar la aplicación, observamos que todas las funciones del panel permanecen inaccesibles hasta que se introduce el PIN correcto. Este comportamiento garantiza que solo los usuarios autorizados puedan acceder a las funciones administrativas. En esta etapa, estamos orgullosos de nuestro progreso, pero reconocemos que aún no hemos alcanzado los límites de nuestro desarrollo. Entendemos que nuestras medidas de seguridad aún deben mejorarse, ya que pueden ser vulnerables a hackers avanzados. Sabemos que cada paso que damos es una oportunidad para aprender más sobre la implementación del lenguaje MQL5 y, a medida que avanzamos en nuestras habilidades, podemos alcanzar niveles de seguridad más sólidos. A continuación se muestra una imagen que muestra el lanzamiento de la aplicación junto con el resultado deseado.
Lanzamiento del panel
Conclusión
En este proyecto, la implementación de un mecanismo de autenticación de inicio de sesión mejoró significativamente la seguridad del Panel de administración, lo cual es vital para proteger las funcionalidades confidenciales. Al exigir una contraseña antes de conceder acceso a las funciones de administración, el programa mitiga el uso no autorizado y garantiza que solo los usuarios verificados puedan gestionar ajustes y operaciones cruciales. El diseño se refuerza con una contraseña global claramente definida y una interfaz fácil de usar para introducir las credenciales.
A medida que avanzamos en nuestro Panel de administración, nos centraremos en mejoras críticas, como la transición de contraseñas codificadas a credenciales gestionadas de forma segura para evitar vulnerabilidades, la incorporación de la autenticación multifactorial para mayor seguridad y la optimización continua de la experiencia de inicio de sesión.
Por otro lado, reconocemos que una vez compilado el código, resulta difícil para cualquiera que no tenga acceso al código fuente acceder a él, gracias a las funciones de seguridad contra la descompilación que ofrece MQL5. Esta capa adicional de protección ayuda a proteger nuestra aplicación contra el acceso no autorizado y la ingeniería inversa.
¡No dudes en probarlo en tus proyectos! Agradezco los comentarios y opiniones, ya que sus aportaciones nos ayudan a mejorar y perfeccionar nuestro trabajo. Sus opiniones son muy valiosas para nosotros, ya que nos ayudan a seguir desarrollando y mejorando nuestras aplicaciones. Consulte el archivo adjunto a continuación.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16079





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
El artículo Creación de un panel de operaciones de administrador en MQL5 (Parte IV) ha sido publicado: Seguridad de inicio de sesión:
Autor: Clemence Benjamin
Al intentar compilar:
'Admin_Panel.mq5' 1
Comercio.mqh
Objeto.mqh
StdLibErr.mqh
OrderInfo.mqh
HistoryOrderInfo.mqh
InformaciónPosición.mqh
DealInfo.mqh
Dialog.mqh
WndContainer.mqh
Wnd.mqh
Rect.mqh
Defines.mqh
ArrayObj.mqh
Array.mqh
WndClient.mqh
Panel.mqh
WndObj.mqh
ChartObjectsTxtControls.mqh
ChartObject.mqh
Scrolls.mqh
BmpButton.mqh
ChartObjectsBmpControls.mqh
Editar.mqh
Gráfico.mqh
Button.mqh
Label.mqh
Up.bmp' como recurso "::res\Up.bmp" 1
ThumbVert.bmp' como recurso "::res\ThumbVert.bmp" 1
Down.bmp' como recurso "::res\Down.bmp" 1
Left.bmp' como recurso "::resLeft.bmp" 1
ThumbHor.bmp' como recurso "::res\ThumbHor.bmp" 1
Right.bmp' como recurso "::res\Right.bmp" 1
Close.bmp' como recurso "::res\Close.bmp" 1
Restore.bmp' como recurso "::res\Restore.bmp" 1
Turn.bmp' como recurso "::res\Turn.bmp" 1
posible pérdida de datos debido a la conversión de tipo de 'long' a 'int' Admin_Panel(4)_.mq5 161 49
'UpdateThemeColors' - identificador no declarado Admin_Panel(4)_.mq5 390 16
darkTheme' - operador esperado Admin_Panel(4)_.mq5 390 34
SetTextColor' - identificador no declarado Admin_Panel(4)_.mq5 397 14
textColor' - operador esperado Admin_Panel(4)_.mq5 397 27
SetBackgroundColor' - identificador no declarado Admin_Panel(4)_.mq5 398 14
bgColor' - operador esperado Admin_Panel(4)_.mq5 398 33
SetBorderColor' - identificador no declarado Admin_Panel(4)_.mq5 399 14
borderColor' - operador esperado Admin_Panel(4)_.mq5 399 29
SetTextColor' - identificador no declarado Admin_Panel(4)_.mq5 424 12
textColor' - operador esperado Admin_Panel(4)_.mq5 424 25
SetBackgroundColor' - identificador no declarado Admin_Panel(4)_.mq5 425 12
bgColor' - operador esperado Admin_Panel(4)_.mq5 425 31
SetBorderColor' - identificador no declarado Admin_Panel(4)_.mq5 426 12
'borderColor' - se esperaba algún operador Admin_Panel(4)_.mq5 426 27
14 errores, 1 advertencias 15 2