
MQL5で取引管理者パネルを作成する(第4回):ログインセキュリティ層
目次
はじめに
セキュリティはあらゆる専門分野において最も重要であり、その重要性を無視することはできません。不正アクセスの脅威が絶えず存在するため、潜在的な侵入者から管理パネルを保護することが重要です。権限のない個人がアクセスした場合、パネルを簡単に操作し、放送コミュニティとのコミュニケーション活動を危険にさらす可能性があります。このシステムの主な目的は、信頼性の高い通信を促進することであり、EAレベルで機能を強化できる一方で、侵入のリスクは依然として大きいままです。
ダッシュボードにアクセスする攻撃者は、ユーザーに誤解を招くメッセージを送信し、混乱を引き起こし、システム管理者の評判を損なう可能性があります。これらのリスクを軽減するには、正しい資格情報なしでの主要機能へのアクセスを制限するセキュリティ層を実装することが不可欠であると考えています。セキュリティに対するこの率直なアプローチは、パネルを保護するだけでなく、コミュニケーションの整合性とコミュニティの信頼を維持するのにも役立ちます。
ログインパネル
MQL5のセキュリティの概要
MQL5は、ソースコードとコンパイルされたファイル(EX5)の両方を保護し、知的財産を保護し、不正使用を防止するように設計された包括的なセキュリティ機能を提供します。主なメカニズムには、コンパイルされたファイルの暗号化、アカウントベースおよび時間ベースのライセンス、追加の保護のための外部DLLとの統合などがあります。プラットフォームはコードの信頼性を検証するためのデジタル署名をサポートし、MetaQuotesはコンパイルと難読化を通じてコード保護を提供し、リバースエンジニアリングを阻止します。MQL5マーケットを通じて配布される製品については、追加の暗号化により、ライセンスを取得したユーザーのみがソフトウェアにアクセスして使用できるようになり、開発者向けの強力なセキュリティフレームワークが確立されます。
2012年、MQL5の著者であるInvesteoは、MQL5プログラムとコードを保護するためのさまざまな方法について説明し、パスワード保護、キー生成、単一アカウントライセンス、時間制限保護、リモートライセンス、安全なライセンス暗号化、高度な逆コンパイル防止方法などの実装技術に関する貴重な洞察を共有しました。彼の研究は、プログラムのセキュリティを強化するための基本的な参考資料として役立ちます。
ディスカッションの目標
セキュリティの重要性を認識し、管理パネルの機能にアクセスするためのパスワード保護の実装について議論することを目的としています。前の画像で示した結果を達成するために使用された手法を詳しく調べ、ユーザーアクセスを効果的に保護する方法に焦点を当てます。
管理パネルにおけるセキュリティ上の懸念事項
プログラムが進化し、新しい機能が組み込まれるにつれて、特に初心者の開発者にとって複雑さが増していることが認識されています。セキュリティに関して、いくつかの重要な関心領域を特定しました。
- 管理パネルへの安全なアクセス
正しいパスコードなしで権限のないユーザーが管理パネルにアクセスできないようにするために、パスワード保護を実装しています。不正アクセスにより、管理者の洞察に依存するトレーダーのコミュニティに意図しないメッセージが広まる可能性があります。意図せずにクイックボタンがランダムにクリックされる可能性があるため、安全なパスコードが必須となります。多くのアプリケーションでは追加の検証に2要素認証(2FA)を採用していますが、私たちは現在、基本的なセキュリティ機能の実装に重点を置いており、今後、より高度なオプションを組み込む予定です。
- Telegram APIメッセージのセキュリティ
また、プログラムの起動時にチャットIDとボットトークンを安全に入力することで、Telegram API経由の通信のセキュリティを最優先します。このアプローチにより、機密データがユーザーの手元で保護されたままになります。Telegramは、標準チャット用のMTProtoプロトコルによるトランスポート層セキュリティや、シークレットチャット用のエンドツーエンドの暗号化など、強力なセキュリティ機能を採用してユーザーの通信を保護します。さらに、Telegramは2FAをサポートしており、ユーザーはアクティブなセッションを管理し、アカウントのセキュリティを強化できます。Telegramのセキュリティプロトコルは強力ですが、侵害されたデバイスによってこれらの保護が損なわれる可能性があるため、ユーザーはデバイスが安全であることも確認する必要があります。
(第3回)の簡単な要約
前回の議論では、テーマ管理のための方法を組み込むことについて触れました。ただし、MetaTrader 5プラットフォームのアップデート中に変更される可能性のあるファイルを扱っていました。アップデートがリリースされるたびに、再起動時に自動的にダウンロードされ、インストールされます。以下は、更新後にコンパイルしようとしたときに発生したエラーを示すコードスニペットです。
'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
一時的な解決策
問題を解決するには、まず問題の原因を理解することが重要です。前述したように、プラットフォームの更新により、使用していたライブラリがデフォルトの状態にリセットされます。その結果、テーマ管理のために実装したメソッドが有効ではなくなり、エラーが発生するようになりました。これを解決するには、更新されたファイル(Dialog.mqh、Edit.mqh、Button.mqh)を、前回の記事に添付した拡張バージョンで上書きする必要があります。インクルードファイルのフォルダは、下の画像に示すように見つけることができます。
dialog.mqhルートフォルダを簡単に見つける
恒久的な解決策
Dialog.mqhおよび使用中のその他の関連ファイルの名前をExtended_Dialog.mqhに変更し、それに応じてコードを調整できますが、古いファイル名を参照する#include文を新しい名前を反映するように更新するようにしてください。さらに、それを参照する可能性のある他の依存関係を確認し、必要に応じて更新する必要があります。これらの変更をおこなった後、プロジェクトを再コンパイルして潜在的なエラーがないか確認し、機能を徹底的にテストしてすべてが正しく動作することを確認します。これにより、新しい名前で個別に保存されますが、元のファイルは保持されます。
たとえば、ファイルをExtended_Dialog.mqhとしてすでに保存している場合は、管理パネルに移動して次のようにコードを調整できます。
#include <Controls\Extended_Dialog.mqh> #include <Controls\Extended_Button.mqh> #include <Controls\Extended_Edit.mqh> #include <Controls\Label.mqh>
別の名前で保存する利点
組み込みバージョンにはない機能を追加または調整することで、機能をニーズに合わせてカスタマイズする機能を提供します。このカスタマイズにより、要件を満たす独自のインターフェイスを作成できます。さらに、カスタムファイル名を使用すると、組み込みライブラリやサードパーティライブラリとの競合を回避でき、名前の重複による予期しない動作のリスクを軽減できます。名前を変更したファイルで拡張機能を分離すると、元のダイアログを使用する可能性のある他の組み込み機能によるカスタマイズの影響を受けなくなり、外部の変更による干渉を受けずにプロジェクトを開発および保守できるようになります。
管理パネルへのパスワード保護の統合
このプロジェクトでは、文字と数字の両方を含むことができる文字列型のパスワードを使用して、複雑さを高め、条件付きパスワード保護を実装します。4桁のPINは単純に思えるかもしれませんが、推測するのは困難です。管理パネルでは、Dialogクラスを使用して、ログイン時にユーザーにパスワードの入力を求め、パスワードの入力が成功した場合にのみメインパネルの機能が表示されるように条件を設定します。
管理パネルプログラムの開発を継続する中で、私たちの主な焦点は、許可されたユーザーのみが機密性の高い管理機能にアクセスできるように、強力なログインセキュリティを確立することにあります。私たちは、不正アクセスからシステムを保護することの重要性を認識し、MQL5を実装して製品を保護する方法について検討してきました。
認証メカニズム
管理パネルを保護するために、機能へのアクセスを許可する前にユーザーにパスワードの入力を求める、簡単なパスワードベースの認証メカニズムを実装しています。この選択は、プログラムの重要なコンポーネントにアクセスするための前提条件としてユーザーのIDを検証するという当社の取り組みを反映しています。
// Show authentication input dialog bool ShowAuthenticationPrompt() { if (!authentication.Create(ChartID(), "Authentication", 0, 100, 100, 500, 300)) { Print("Failed to create authentication dialog"); return false; } }
ShowAuthenticationPrompt関数では、認証プロセスを通じてユーザーを効果的にガイドするユーザーフレンドリーなインターフェイスを設計します。パスワード入力専用のダイアログを作成することで、管理パネルへの主要なアクセスポイントが直感的でありながら安全であることを保証します。
理解を深めるために、ダイアログ作成のコードを以下のスニペットに概説し、その動作を説明するコメントも添えました。軸と座標についてもう一度確認したい場合は、第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 ))
認証ダイアログを作成したら、値は異なりますが、他のUI要素を同様に配置していきます。このプロセスは、ユーザーが資格情報を入力できるパスワード入力ボックスの作成から始まり、その後に必須のボタンが表示されます。具体的には、[Login]ボタンと[Close]ボタンという2つのメインボタンに焦点を当てます。[Login]ボタンは入力したパスワードを送信するために使用され、[Close]ボタンはユーザーがパスワードを知らない場合にダイアログを終了するオプションを提供します。以下は、これらのボタンとパスワードプロンプトラベルを作成するロジックを示すコードスニペットです。
// 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 }
パスワード管理
現在、初期テストには単純なハードコードされたパスワードを使用しており、これにより機能のプロトタイプを迅速に作成できます。ただし、このアプローチには、コードが侵害された場合に総当たり攻撃に対して脆弱になるなどのリスクが伴うことは十分に理解しています。
// Default password for authentication string Password = "2024";
ハードコードされたパスワードを使用すると開発が迅速化されることは認識していますが、将来のアップデートでは、より安全な解決策に移行する必要があります。具体的には、暗号化された構成ファイルを実装するか、より高度なユーザーアカウント管理システムを利用してセキュリティを強化する必要があります。
ユーザー入力の処理
セキュリティを強化するには、認証ダイアログでパスワード入力フィールドが明確に定義されていることを確認する必要があります。ユーザーにパスワードの入力を促し、その入力を保存されているパスワードと照合することで、シームレスで安全なログイン体験を目指しています。// 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 } }
OnLoginButtonClick関数では、プログラムは入力されたパスワードが保存されているパスワードと一致するかどうかを確認します。正常に入力されると、認証ダイアログが非表示になり、ユーザーに管理パネルが表示されます。パスワードが間違っている場合は、入力フィールドがクリアされ、ユーザーに再試行を促すプロンプトが表示されるため、ユーザーはログイン中にプロセスを明確に理解しながら安全に作業を進めることができます。
また、終了ロジックを担当する[Close]ボタンのハンドラもあります。このボタンをクリックすると、認証ダイアログが閉じられ、エキスパートアドバイザー(EA)がチャートから完全に削除され、管理機能へのアクセスが残らないようになります。このアクションにより、セキュリティが強化され、認証を続行しないことを選択したユーザーに明確な終了経路が提供されます。ハンドラの定義方法は次のとおりです。
//+------------------------------------------------------------------+ //| Handle close button click for authentication | //+------------------------------------------------------------------+ void OnCloseAuthButtonClick() { authentication.Destroy(); ExpertRemove(); // Remove the expert if user closes the authentication dialog Print("Authentication dialog closed."); }
このハンドラでは、authentication.Destroy()メソッドはダイアログを効果的に閉じ、ExpertRemove()はEAがビューから完全に削除されることを保証し、アプリケーションの全体的なセキュリティを強化します。
メインプログラムに完全に組み込まれています。
//+------------------------------------------------------------------+ //| Admin Panel.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com/ja/users/billionaire2024/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/ja/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; } }
テストと結果
コードは正常にコンパイルされ、アプリケーションを起動すると、正しいPINが入力されるまでパネルの全機能にアクセスできないことがわかりました。この動作により、許可されたユーザーのみが管理機能にアクセスできるようになります。現段階では、進歩を誇りに思っていますが、まだ開発の限界に達していないことを認識しています。このセキュリティ対策は高度なハッカーに対して脆弱である可能性があるため、さらに強化する必要があることは理解しています。私たちが踏む一歩一歩がMQL5言語の実装についてさらに学ぶ機会であり、スキルが向上するにつれて、より強固なセキュリティレベルを達成できることを私たちは知っています。以下は、アプリケーションの起動と望ましい結果を示す画像です。
パネルの立ち上げ
結論
このプロジェクトでは、ログイン認証メカニズムの実装により、機密機能の保護に不可欠な管理パネルのセキュリティが大幅に強化されました。管理機能へのアクセスを許可する前にパスワードを要求することで、プログラムは不正使用を軽減し、検証されたユーザーのみが重要な設定と操作を管理できるようにします。この設計は、明確に定義されたグローバルパスワードと、資格情報を入力するためのユーザーフレンドリーなインターフェイスによって強化されています。
管理パネルを進化させるにあたり、脆弱性を防ぐためにハードコードされたパスワードから安全に管理された認証情報への移行、セキュリティ強化のための多要素認証の組み込み、ログインエクスペリエンスの継続的な最適化など、重要な改善に重点を置きます。
一方、MQL5が提供する逆コンパイル防止セキュリティ機能により、コードがコンパイルされると、ソースコードにアクセスできないユーザーがアクセスすることが困難になることも認識しています。この追加された保護層は、不正アクセスやリバースエンジニアリングからアプリケーションを保護するのに役立ちます。
ぜひ自分のプロジェクトで試してみてください。皆様のご意見は私たちの仕事の改善と洗練に役立ちます。コメントやフィードバックをお待ちしています。アプリケーションの開発と強化を継続していく上で、皆様のご意見は貴重なものとなります。下記の添付ファイルを確認してください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16079





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索