English Русский 中文 Español Deutsch 日本語
preview
Criação de um painel de administração de trading no MQL5 (Parte IV): Segurança no login

Criação de um painel de administração de trading no MQL5 (Parte IV): Segurança no login

MetaTrader 5Exemplos |
40 1
Clemence Benjamin
Clemence Benjamin

Conteúdo:


Introdução

A segurança é prioridade máxima em qualquer área de atuação e não podemos nos dar ao luxo de ignorar sua importância. Diante da ameaça constante de acessos não autorizados, é fundamental proteger o painel de administração contra possíveis invasores. Se pessoas não autorizadas obtiverem acesso, poderão facilmente controlá-lo e comprometer nossos esforços. O objetivo principal do sistema é garantir uma comunicação confiável. Mesmo que possamos melhorar a funcionalidade no nível do EA (Expert Advisor), os riscos de invasão continuam significativos.

Um invasor com acesso ao painel de controle pode enviar mensagens enganosas aos usuários, causando confusão e prejudicando a reputação do administrador do sistema. Para reduzir esses riscos, acredito que é necessário implementar um nível de segurança que restrinja o acesso a funções-chave sem as credenciais corretas. Essa abordagem de proteção não só defenderá nosso painel, como também ajudará a preservar a integridade de nossas comunicações e a confiança da comunidade.

Painel de login

Painel de login


Visão geral da segurança no MQL5

O MQL5 oferece um conjunto completo de recursos de segurança, projetados para proteger tanto o código-fonte quanto os arquivos compilados (EX5), garantindo a proteção da propriedade intelectual e evitando o uso não autorizado. Entre os principais mecanismos estão a criptografia dos arquivos compilados, o licenciamento baseado em conta e tempo e a integração com bibliotecas DLL externas para proteção adicional. A plataforma também suporta assinaturas digitais para verificar a autenticidade do código. A MetaQuotes assegura a proteção do código por meio de compilação e ofuscação, prevenindo a engenharia reversa. Para os produtos distribuídos pelo MQL5 Market, a criptografia adicional garante que apenas os usuários licenciados tenham acesso ao software, criando uma estrutura de segurança robusta para os desenvolvedores.

Em 2012, o usuário MQL5 Investeo analisou vários métodos de proteção de programas e de código MQL5 e compartilhou ideias valiosas sobre a implementação de recursos como proteção por senha, geração de chaves, licenciamento para uma única conta, proteção com limite de tempo, licenças remotas, criptografia segura de licenças e técnicas avançadas contra descompilação. Seu trabalho serve como base fundamental de conhecimento para o aprimoramento da segurança de programas.

Objetivo da discussão:

Reconhecendo a importância da segurança, nosso propósito é discutir a implementação de proteção por senha para o acesso às funções do painel de administração. Examinaremos em detalhes os métodos utilizados para alcançar os resultados demonstrados na imagem anterior, com foco em como proteger de forma eficaz o acesso dos usuários.

Possíveis áreas de vulnerabilidade no nosso painel de administração:

À medida que nosso programa evolui e incorpora novas funcionalidades, sua complexidade aumenta, especialmente para desenvolvedores iniciantes. Destacamos a seguir algumas áreas-chave de interesse em relação à segurança:

  • Acesso protegido ao painel de administração:

Para garantir que nenhum usuário não autorizado possa acessar o painel de administração, implementamos proteção por senha. O acesso não autorizado pode resultar na disseminação de mensagens indesejadas entre a comunidade de traders, que confia nas informações do administrador. Cliques acidentais em botões de atalho exigem uma senha robusta. Embora muitos aplicativos utilizem a autenticação em dois fatores (2FA) para uma verificação adicional, no momento estamos focados na implementação de recursos básicos de segurança, com a intenção de incorporar opções mais avançadas no futuro.

  • Segurança das mensagens via API do Telegram:

Damos também prioridade máxima à segurança da comunicação via API do Telegram, inserindo de maneira segura o ID da conversa e o token do bot durante a inicialização do programa. Essa abordagem garante que os dados confidenciais do usuário permaneçam protegidos. O Telegram utiliza recursos robustos de segurança para proteger as conversas dos usuários, incluindo segurança de transporte (Transport Layer Security) com ajuda do protocolo MTProto para chats padrão e criptografia de ponta a ponta para chats secretos. Além disso, o Telegram oferece suporte a 2FA, permitindo que os usuários gerenciem sessões ativas e aumentem a segurança da conta. Apesar de os protocolos de segurança do Telegram serem confiáveis, os usuários também devem garantir a segurança de seus próprios dispositivos.


Breve repetição da Parte III

Em nossa discussão anterior, abordamos a implementação de métodos para o gerenciamento de temas. No entanto, estávamos trabalhando com arquivos suscetíveis a alterações durante as atualizações da plataforma MetaTrader 5. Cada vez que uma atualização é lançada, ela é baixada e instalada automaticamente ao reiniciar o sistema. Abaixo, há um trecho de código que ilustra os erros encontrados ao tentar compilar após as atualizações.

'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

Solução temporária

Para resolvê-lo, é preciso primeiro compreender sua origem. Como explicado anteriormente, as atualizações da plataforma restauram as bibliotecas utilizadas em seus estados padrão. Consequentemente, os métodos implementados para gerenciar temas deixam de funcionar e começamos a enfrentar erros. Para solucionar esse problema, precisamos sobrescrever os arquivos atualizados (Dialog.mqh, Edit.mqh e Button.mqh) com as versões estendidas, anexadas ao artigo anterior. É possível localizar a pasta de arquivos de inclusão conforme mostrado na figura a seguir.

Navegando até a pasta raiz dos arquivos de inclusão.

Navegando até a pasta raiz dialog.mqh

Solução permanente:

Podemos renomear o Dialog.mqh e outros relacionados para Extended_Dialog.mqh e ajustar o código de acordo. É essencial atualizar as instruções #include que faziam referência ao nome antigo para que reflitam o novo nome. Além disso, devemos verificar se existem outras dependências que também o utilizam e atualizá-las, se necessário. Após essas alterações, devemos recompilar nosso projeto para identificar possíveis erros e testar minuciosamente sua funcionalidade, garantindo que tudo esteja funcionando corretamente. Dessa forma, manteremos uma cópia separada com o novo nome, preservando o arquivo original.

Por exemplo, se já salvamos o arquivo como Extended_Dialog.mqh, podemos ir até o nosso painel de administração e ajustar o código da seguinte forma:

#include <Controls\Extended_Dialog.mqh>
#include <Controls\Extended_Button.mqh>
#include <Controls\Extended_Edit.mqh>
#include <Controls\Label.mqh>

Vantagens de manter um nome diferente

Salvar com um nome diferente permite adaptar a funcionalidade às suas necessidades, adicionando ou ajustando recursos que não estão presentes na versão integrada. Essa personalização ajuda a criar uma interface única, alinhada aos seus requisitos. Além disso, o uso de nomes de arquivos personalizados evita conflitos com bibliotecas internas ou de terceiros, reduzindo o risco de comportamentos inesperados causados por nomes coincidentes. Ao manter as alterações feitas em um arquivo renomeado, você também protege suas configurações contra interferências de outras funções internas que possam utilizar o Dialog original, garantindo que você possa desenvolver e manter seu projeto sem ser afetado por mudanças externas.


Integração de proteção por senha no painel de administração

Neste projeto, implementamos uma proteção por senha condicional, utilizando uma sequência de caracteres que pode conter letras e números, aumentando assim o nível de complexidade. Embora um PIN de quatro dígitos possa parecer simples, ele é difícil de adivinhar. No painel de administração, usamos a classe Dialog para solicitar a senha do usuário no momento do login e para estabelecer que as funções do painel principal só sejam exibidas após a inserção bem-sucedida da senha correta.

À medida que continuamos a desenvolver o programa do painel de administração, damos atenção especial a garantir uma segurança de login robusta, de modo que apenas usuários autorizados possam acessar funções confidenciais.

Mecanismo de autenticação

Para proteger o painel de administração, implementamos um mecanismo simples de autenticação baseado em senha, que solicita ao usuário a senha antes de conceder acesso a qualquer funcionalidade.

// Show authentication input dialog
bool ShowAuthenticationPrompt()
{
    if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300))
    {
        Print("Failed to create authentication dialog");
        return false;
    }
   
}

Na função ShowAuthenticationPrompt criamos uma interface amigável que conduz o usuário de forma eficiente pelo processo de autenticação. Ao desenvolver um diálogo específico para a entrada da senha, garantimos que o principal ponto de acesso ao painel de administração permaneça seguro e, ao mesmo tempo, intuitivo.

Para melhor compreensão, apresento abaixo um trecho de código que cria a janela de diálogo, acompanhado de comentários. Se quiser relembrar conceitos sobre eixos e coordenadas, consulte a Parte 1.  

// 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
))

Após criar a janela de diálogo de autenticação, passamos para a disposição dos demais elementos da interface do usuário, mas com valores diferentes. O processo começa com a criação do campo de entrada de senha, no qual o usuário insere suas credenciais e, em seguida, clica nos botões necessários. Em especial, destacamos dois botões principais: Login (entrar) e Close (fechar). O botão Login é usado para enviar a senha inserida, enquanto o botão Close fecha a janela de diálogo. Abaixo está um trecho de código que ilustra a lógica de criação desses botões, bem como o texto do pedido de senha.

 // 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
}

Gerenciamento de senhas

Atualmente, para os testes iniciais, utilizamos uma senha simples, codificada diretamente no programa, o que nos permite criar rapidamente protótipos de funcionalidade. No entanto, entendemos plenamente que essa abordagem traz riscos, como vulnerabilidade a ataques de força bruta no caso de o código ser comprometido.

// Default password for authentication
string Password = "2024";

Embora o uso de uma senha fixa acelere nosso desenvolvimento, em futuras atualizações precisaremos migrar para uma solução mais segura, especificamente implementando arquivos de configuração criptografados ou utilizando um sistema mais avançado de gerenciamento de contas de usuários para aumentar a proteção.

Tratamento da entrada do usuário

Para reforçar a segurança, é essencial garantir que o campo de entrada de senha esteja claramente definido na janela de diálogo de autenticação. Ao ajudar o usuário a inserir a senha e verificar os dados digitados com a senha armazenada, buscamos proporcionar um processo de login seguro e sem interrupções.
// 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
    }
}

Na função OnLoginButtonClick o programa verifica se a senha inserida coincide com a armazenada. Em caso de sucesso, a janela de autenticação é ocultada e o usuário tem acesso ao painel de administração. Se a senha estiver incorreta, o campo de entrada é limpo e o usuário é convidado a tentar novamente.

Também implementamos um manipulador para o botão Close, responsável pela lógica de saída. Ao clicar nesse botão, a janela de autenticação é fechada e o EA é removido do gráfico. Essa ação reforça a segurança e oferece um caminho claro de saída para os usuários que optarem por não realizar a autenticação. Definição do manipulador:

//+------------------------------------------------------------------+
//| Handle close button click for authentication                     |
//+------------------------------------------------------------------+
void OnCloseAuthButtonClick()
{
    authentication.Destroy();
    ExpertRemove(); // Remove the expert if user closes the authentication dialog
    Print("Authentication dialog closed.");
}

No manipulador, o método authentication. Destroy() fecha o diálogo, enquanto ExpertRemove() garante que o EA desapareça completamente, aumentando a segurança geral da aplicação.

Integração completa ao 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;
    }
}


Testes e resultados

Nosso código foi compilado com sucesso e, após a execução do aplicativo, constatamos que todas as funções do painel permanecem indisponíveis até que o PIN correto seja inserido. Esse comportamento garante que apenas usuários autorizados possam acessar as funções do painel. Temos consciência de que nossas medidas de segurança ainda precisam evoluir, pois podem ser vulneráveis a hackers experientes. Sabemos que cada passo dado é uma oportunidade de aprender mais sobre a implementação no MQL5 e, à medida que desenvolvemos nossas habilidades, poderemos alcançar níveis de proteção mais robustos. Abaixo, mostramos a execução do aplicativo e o resultado esperado.

Painel de administração seguro

Execução do painel



Considerações finais

Neste projeto, a implementação do mecanismo de autenticação no login aumentou significativamente a segurança do painel de administração, algo essencial para proteger funcionalidades confidenciais. Ao solicitar uma senha antes de conceder acesso às funções administrativas, o programa reduz o risco de uso não autorizado e garante que apenas usuários verificados possam gerenciar configurações e operações críticas. Implementamos um sistema de senha global e uma interface amigável para entrada das credenciais.

À medida que aprimoramos nosso painel de administração, focaremos em melhorias importantes, como a substituição de senhas fixas por credenciais gerenciadas de forma segura para evitar vulnerabilidades, a implementação de autenticação multifator para segurança adicional e a otimização contínua do processo de login.

Por outro lado, reconhecemos que, após a compilação, qualquer usuário sem acesso ao código-fonte terá dificuldade em explorá-lo devido aos recursos de proteção contra descompilação oferecidos pelo MQL5. Esse nível extra de proteção contribui para resguardar nosso aplicativo contra acessos não autorizados e engenharia reversa.

Experimente aplicar essa funcionalidade em seus próprios projetos! Comentários e feedback são muito bem-vindos. Suas ideias podem nos ajudar a melhorar e aperfeiçoar ainda mais nosso trabalho.

Ao conteúdo

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16079

Arquivos anexados |
Admin_Panel_.mq5 (18.66 KB)
Últimos Comentários | Ir para discussão (1)
SERGEI NAIDENOV
SERGEI NAIDENOV | 23 mai. 2025 em 19:53

Ao tentar compilar:

'Admin_Panel.mq5' 1

Trade.mqh

Object.mqh

StdLibErr.mqh

OrderInfo.mqh

HistoryOrderInfo.mqh

PositionInfo.mqh

DealInfo.mqh

Dialog.mqh

WndContainer.mqh

Wnd.mqh

Rect.mqh

Definições.mqh

ArrayObj.mqh

Matriz.mqh

WndClient.mqh

Painel.mqh

WndObj.mqh

ChartObjectsTxtControls.mqh

ChartObject.mqh

Scrolls.mqh

BmpButton.mqh

ChartObjectsBmpControls.mqh

Editar.mqh

Chart.mqh

Botão.mqh

Rótulo.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 "::res\Left.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

Possível perda de dados devido à conversão de tipo de 'long' para 'int' Admin_Panel(4)_.mq5 161 49

'UpdateThemeColors' - identificador não declarado Admin_Panel(4)_.mq5 390 16

'darkTheme' - algum operador esperado Admin_Panel(4)_.mq5 390 34

'SetTextColor' - identificador não declarado Admin_Panel(4)_.mq5 397 14

'textColor' - algum operador esperado Admin_Panel(4)_.mq5 397 27

'SetBackgroundColor' - identificador não declarado Admin_Panel(4)_.mq5 398 14

'bgColor' - algum operador esperado Admin_Panel(4)_.mq5 398 33

'SetBorderColor' - identificador não declarado Admin_Panel(4)_.mq5 399 14

'borderColor' - algum operador esperado Admin_Panel(4)_.mq5 399 29

'SetTextColor' - identificador não declarado Admin_Panel(4)_.mq5 424 12

'textColor' - algum operador esperado Admin_Panel(4)_.mq5 424 25

'SetBackgroundColor' - identificador não declarado Admin_Panel(4)_.mq5 425 12

'bgColor' - algum operador esperado Admin_Panel(4)_.mq5 425 31

'SetBorderColor' - identificador não declarado Admin_Panel(4)_.mq5 426 12

'borderColor' - algum operador esperado Admin_Panel(4)_.mq5 426 27

14 erros, 1 advertência 15 2


Como criar um diário de negociações com MetaTrader e Google Sheets Como criar um diário de negociações com MetaTrader e Google Sheets
Crie um diário de negociações usando o MetaTrader e o Google Sheets! Você aprenderá como sincronizar seus dados de negociação via HTTP POST e recuperá-los usando requisições HTTP. Ao final, você terá um diário de negociações que ajudará a acompanhar suas operações de forma eficaz e eficiente.
Como integrar o conceito de Smart Money (OB) em combinação com o indicador Fibonacci para entrada ideal na operação Como integrar o conceito de Smart Money (OB) em combinação com o indicador Fibonacci para entrada ideal na operação
As SMC (Order Block) são áreas-chave em que os traders institucionais realizam compras ou vendas significativas. Após uma movimentação considerável de preço, os níveis de Fibonacci ajudam a identificar um possível recuo desde o máximo recente de oscilação (swing high) até o mínimo de oscilação (swing low), de modo a determinar o ponto de entrada ideal na operação.
Simplificando a negociação com base em notícias (Parte 4): Aumentando o desempenho Simplificando a negociação com base em notícias (Parte 4): Aumentando o desempenho
Neste artigo, serão apresentados métodos para melhorar o desempenho do EA no testador de estratégias, além da implementação de um código para dividir o horário dos eventos de notícias em categorias por hora. O acesso a esses eventos será permitido apenas no horário especificado para cada um. Isso permite que o EA gerencie operações de maneira eficiente com base nos eventos, tanto em condições de alta quanto de baixa volatilidade.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 41): Deep-Q-Networks Técnicas do MQL5 Wizard que você deve conhecer (Parte 41): Deep-Q-Networks
O Deep-Q-Network é um algoritmo de aprendizado por reforço que utiliza redes neurais para projetar (estimar) o próximo valor-Q e a ação ideal durante o processo de treinamento de um módulo de aprendizado de máquina. Já consideramos um algoritmo alternativo de aprendizado por reforço, o Q-Learning. Este artigo, portanto, apresenta outro exemplo de como um MLP treinado com aprendizado por reforço pode ser usado dentro de uma classe de sinal personalizada.