EAの認証システムブラックリスト方式
EAの認証システム:ブラックリスト方式の利点と活用法
エキスパートアドバイザー(EA)の配布と管理において、ホワイトリスト形式の認証システムが一般的です。しかし、ブラックリスト方式を採用することで、柔軟性が高まり、特に「IB(Introducing Broker)紐付け」のような特定の条件を満たしたユーザーに対して機能をアップグレードさせる場合に効果的です。
ホワイトリスト方式とブラックリスト方式の違い
伝統的なホワイトリスト方式では、事前に認証されたユーザーのみがEAを使用できます。これに対して、ブラックリスト方式では基本的に全員がEAを利用できるが、特定の条件を満たさないユーザー(例えばIBに紐づいていないユーザー)を後から制限する形をとります。
ブラックリスト方式の利点
柔軟な配布:初めにEAを広く配布し、その後で特定のユーザーに対して制限を加えることができます。これにより、ユーザーベースの迅速な拡大が可能です。
動的な管理:ユーザーの状態や条件が変化した場合(例:IBに紐付けされた場合)、リアルタイムで機能の制限やアップグレードを行えます。
マーケティング戦略:特定の条件を満たすことで追加機能が得られるというインセンティブを設定することで、ユーザーに対するマーケティング効果を高めることができます。
活用方法:IB紐付けによる機能アップグレード
ブラックリスト方式を活用する一つの方法は、IB紐付けを条件としてEAの追加機能を提供することです。例えば、すべてのユーザーに基本機能を提供し、IBに紐付けされたユーザーには高度なトレーディングツールやカスタマイズオプションを解放します。このアプローチは、新しいIBクライアントの獲得を促進すると同時に、EAの利用価値を高めることができます。
実装上の考慮事項
ブラックリスト方式を採用する場合、以下の点に注意が必要です:
セキュリティ:不正使用を防ぐために、セキュアな認証システムを構築することが重要です。
ユーザープライバシー:ユーザーのプライバシーを尊重し、関連する法律や規制に準拠することが必要です。
ユーザー体験:機能制限やアップグレードのプロセスがユーザーにとって透明でわかりやすいことを確保します。
まとめ
ブラックリスト方式の認証システムは、EAの配布と管理において柔軟性と効果的なマーケティングツールを提供します。特にIB紐付けなど特定の条件を満たしたユーザーへの機能アップグレードの提供において、このアプローチは大きな利点を持っています。ただし、実装にあたってはセキュリティ、プライバシー保護、ユーザー体験の3つの要素を慎重に考慮する必要があります。
以下はソースコードです。流れは前回のものと同じです。
`auth()` 関数では、HTTPリクエストを通じて得られるレスポンスに基づいて、異なる認証ステータスを返します。各ステータスコード(-1、0、1、2、3)は、特定の状況や条件に対応しています。以下に各ステータスコードの意味を説明します。
-1: DLLが許可されていない
このステータスは、MetaTrader 4(MT4)プラットフォームの設定でDLLの使用が許可されていない場合に返されます。
MT4では、セキュリティ上の理由からデフォルトではDLLの使用が制限されています。DLLを使用するためには、ユーザーが明示的に設定で許可する必要があります。
このエラーが発生した場合、ユーザーはMT4の設定を確認し、DLLの使用を許可する必要があります。
0: 仮認証
このステータスは、サーバーからのレスポンスが「0」であった場合、またはレスポンスが存在しない場合に返されます。
仮認証は、ユーザーが特定の条件(例:IBに紐付いている、特定のアカウント番号を持っているなど)を満たしていないが、基本的な機能は使用できる状態を指します。
1: 本認証
このステータスは、サーバーからのレスポンスが「1」であった場合に返されます。
本認証は、ユーザーが必要なすべての条件を満たしているため、EAの全機能が使用可能な状態を指します。
2: 制限
このステータスは、サーバーからのレスポンスが「2」であった場合に返されます。
制限は、ユーザーが特定の条件を満たしていないため、EAの一部機能に制限がかかっている状態を指します。
3: その他の場合
このステータスは、上記の条件に該当しない場合に返されます。例えば、デモモードやテストモードである、アカウント番号が0である、サーバーへの接続に失敗した場合などです。
この状態では、EAは基本的に無効または非機能的な状態となります。
これらのステータスコードは、EAの利用状況や認証プロセスを管理するために重要で、EAの機能やアクセスレベルを適切に制御するために使用されます。
インクルードファイル kyoka .mqh
// kyoka.mqh
// kyoka.mqh
#import "wininet.dll"
int InternetOpenW(string agent, int accessType, string proxyName, string proxyByPass, int flags);
int InternetConnectW(int internet, string serverName, int port, string userName, string password, int service, int flags, int context);
int HttpOpenRequestW(int connect, string verb, string objectName, string version, string referer, char& acceptTypes[], uint flags, int context);
bool HttpSendRequestW(int hRequest, string &lpszHeaders, int dwHeadersLength, uchar &lpOptional[], int dwOptionalLength);
int InternetReadFile(int, uchar &arr[], int, int &byte);
int InternetCloseHandle(int winINet); #import
#define DEFAULT_HTTPS_PORT 443
#define SERVICE_HTTP 3
#define FLAG_SECURE 0x00800000
#define FLAG_PRAGMA_NOCACHE 0x00000100
#define FLAG_KEEP_CONNECTION 0x00400000
#define FLAG_RELOAD 0x80000000
string your_url = "asia-northeast1-optical-legend-0000.cloudfunctions.net";
string your_endpoint = "/kyoka";
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int auth() {
if(IsTesting()) {
Print("Testing mode. Exiting.");
return 3;
}
if(AccountNumber()==0){
Print("Account number is 0. Exiting.");
return 3;
}
string host = your_url;
string UserAgent = "MT4 Authentication";
string nill = "";
Print("Opening Internet session...");
int session = InternetOpenW(UserAgent, 0, nill, nill, 0);
if (session == 0) {
Print("Error opening Internet session: ", GetLastError());
return 3;
}
Print("Connecting to server...");
int connect = InternetConnectW(session, host, DEFAULT_HTTPS_PORT, nill, nill, SERVICE_HTTP, 0, 0);
if (connect == 0) {
Print("Error connecting to server: ", GetLastError());
InternetCloseHandle(session);
return 3;
}
string Vers = "HTTP/1.1";
string Method = "GET";
string accountNumberStr = DoubleToString(AccountNumber(), 0);
string brokerName = AccountCompany();
string Object = your_endpoint + "?account_number=" + accountNumberStr + "&broker_name=" + brokerName;
string Types = "";
char acceptTypes[];
StringToCharArray(Types, acceptTypes);
int context = 0;
Print("Sending HTTP request...");
int hRequest = HttpOpenRequestW(connect, Method, Object, Vers, nill, acceptTypes, FLAG_SECURE | FLAG_KEEP_CONNECTION | FLAG_RELOAD | FLAG_PRAGMA_NOCACHE, context);
string headers = "Content-Type: application/x-www-form-urlencoded\r\n";
uchar body[];
int dwHeadersLength = 0;
int dwOptionalLength = 0;
bool hSend = HttpSendRequestW(hRequest, headers, dwHeadersLength, body, dwOptionalLength);
if (!hSend) {
Print("Error sending request: ", GetLastError());
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
InternetCloseHandle(session);
return 3;
}
uchar ch[100];
string toStr = "";
int dwBytes;
Print("Reading response...");
while(InternetReadFile(hRequest, ch, 100, dwBytes)) {
if(dwBytes <= 0) break;
toStr = CharArrayToString(ch, 0, dwBytes);
}
// Close handles
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
InternetCloseHandle(session);
if(!IsDllsAllowed()) {
Comment("DLLを許可してください。");
Alert("DLLを許可してください。");
return -1;
}
bool A = StringFind(toStr, "0") > 0 || StringToInteger(toStr) == 0;
bool B = StringFind(toStr, "1") > 0 || StringToInteger(toStr) == 1;
bool C = StringFind(toStr, "2") > 0 || StringToInteger(toStr) == 2;
Print("toStr:",toStr);
if(C) {
Comment("制限");
return 2;
}
if(B) {
Comment("本認証");
return 1;
}
if(A) {
Comment("仮認証");
return 0;
}
return 3;
}
string LineNotify(string message, string token) {
if(IsTesting()) {
Print("Testing mode. Exiting.");
return "3";
}
if(AccountNumber()==0){
Print("Account number is 0. Exiting.");
return "3";
}
string headers;
string UserAgent = "LineNotifyAgent";
string nill = "";
string host = "notify-api.line.me";
string Vers = "HTTP/1.1";
string POST = "POST";
string Object = "/api/notify";
// Opening Internet session
int session = InternetOpenW(UserAgent, 0, nill, nill, 0);
if (session == 0) {
Print("Error opening Internet session: ", GetLastError());
return "Error";
}
// Connecting to server
int connect = InternetConnectW(session, host, DEFAULT_HTTPS_PORT, nill, nill, SERVICE_HTTP, 0, 0);
if (connect == 0) {
Print("Error connecting to server: ", GetLastError());
InternetCloseHandle(session);
return "Error";
}
// Preparing the request
char acceptTypes[];
StringToCharArray("", acceptTypes);
int context = 0;
int hRequest = HttpOpenRequestW(connect, POST, Object, Vers, nill, acceptTypes, FLAG_SECURE | FLAG_KEEP_CONNECTION | FLAG_RELOAD | FLAG_PRAGMA_NOCACHE, context);
// Adding headers (including the LINE Notify token)
headers = "Content-Type: application/x-www-form-urlencoded\r\n";
headers += "Authorization: Bearer " + token + "\r\n";
// Preparing the body of the request
string body = "message=" + message;
uchar bodyArr[];
StringToCharArray(body, bodyArr);
// Sending the request
bool hSend = HttpSendRequestW(hRequest, headers, StringLen(headers), bodyArr, ArraySize(bodyArr));
if (!hSend) {
Print("Error sending request: ", GetLastError());
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
InternetCloseHandle(session);
return "Error";
}
// Reading the response
uchar ch[100];
string toStr = "";
int dwBytes;
while(InternetReadFile(hRequest, ch, 100, dwBytes)) {
if(dwBytes <= 0) break;
toStr += CharArrayToString(ch, 0, dwBytes);
}
// Closing handles
InternetCloseHandle(hRequest);
InternetCloseHandle(connect);
InternetCloseHandle(session);
return toStr; // Return the response from LINE Notify
}
EA本体に追加
#property strict
#include <kyoka.mqh>
int ibStatus = 0;
int OnInit() {
ibStatus = auth();
if(ibStatus==-1){return -1;}
return 0;}
GCP
import functions_framework
from google.cloud import firestore
import datetime
from flask import jsonify
# Initialize Firestore client
db = firestore.Client()
@functions_framework.http
def check_account_number(request):
"""HTTP Cloud Function to check account number.
Args:
request (flask.Request): The request object.
Returns:
A Flask response object with the status of the account number.
"""
# Parse request arguments
request_args = request.args
if not request_args or 'account_number' not in request_args or 'broker_name' not in request_args:
print("Invalid request: Missing 'account_number' or 'broker_name'")
return jsonify({'error': 'Invalid request'}), 400
account_number = request_args['account_number']
broker_name = request_args['broker_name']
# Log the received parameters
print(f"Received parameters - Broker Name: {broker_name}, Account Number: {account_number}")
# Reference to the collection and document
broker_ref = db.collection(broker_name).document(account_number)
try:
# Attempt to retrieve the document
doc = broker_ref.get()
if doc.exists:
# Return the status if the document exists
print(f"Document exists - Broker Name: {broker_name}, Account Number: {account_number}")
status = doc.to_dict().get('status', '0')
return jsonify({'status': status})
else:
# Create a new document if it does not exist
new_doc_data = {
'broker_name': broker_name,
'account_number': account_number,
'status': '0', # 仮認証
'update_date': datetime.datetime.now(),
'creation_date': datetime.datetime.now()
}
broker_ref.set(new_doc_data)
print(f"Created new document - Data: {new_doc_data}")
return jsonify({'status': '0'}) # Return the initial status
except Exception as e:
print(f"Error: {str(e)}")
return jsonify({'error': str(e)}), 500
functions-framework==3.*
google-cloud-firestore==2.1.3
flask
追加でHTMLで利用者一覧を取得してステータスに色付けたものです
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Firestore Data</title>
<meta charset="UTF-8">
<style>
table {
border-collapse: collapse;
width: 100%;
table-layout: fixed;
}
th, td {
border: 1px solid black;
padding: 8px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
th {
cursor: pointer;
background-color: #f2f2f2;
}
.status-0 { background-color: #FFFF99; } /* 明るい黄色 */
.status-1 { background-color: #66FF66; } /* 明るい緑 */
.status-2 { background-color: #FF6666; } /* 明るい赤 */
button.status-button {
padding: 4px 8px;
cursor: pointer;
}
.sortable:after {
content: ' \25B2'; /* ▲: ソート可能なことを示す */
}
.sorted-asc:after {
content: ' \25B2'; /* ▲: 昇順 */
}
.sorted-desc:after {
content: ' \25BC'; /* ▼: 降順 */
}
/* 列幅の指定 */
.col-collection { width: 15%; }
.col-id { width: 10%; }
.col-account-number { width: 10%; }
.col-broker-name { width: 15%; }
.col-creation-date { width: 15%; }
.col-update-date { width: 15%; }
.col-status { width: 10%; }
.col-actions { width: 10%; }
</style>
</head>
<body>
<h2>Firestore Data</h2>
<p>列をクリックすると昇順・降順が変更できます。</p>
<div id="password-protection">
<label for="password">パスワードを入力してください:</label>
<input type="password" id="password">
<button onclick="checkPassword()">確認</button>
</div>
<div id="data-container" style="display: none;">
<table id="data-table"></table>
</div>
<script>
const correctPassword = '1234';
function checkPassword() {
const password = document.getElementById('password').value;
if (password === correctPassword) {
document.getElementById('password-protection').style.display = 'none';
document.getElementById('data-container').style.display = 'block';
fetchData();
} else {
alert('パスワードが違います。もう一度入力してください。');
}
}
const apiUrl = 'https://';
const updateApiUrl = 'https://';
let tableData = [];
let sortOrder = {};
function createTable(data) {
const table = document.getElementById('data-table');
table.innerHTML = '';
const headerRow = table.insertRow();
const columns = ['collection', 'id', 'account_number', 'broker_name', 'creation_date', 'update_date', 'status', 'Actions'];
columns.forEach((key, index) => {
const headerCell = headerRow.insertCell();
headerCell.textContent = key;
headerCell.classList.add('sortable', `col-${key.toLowerCase().replace('_', '-')}`);
if (key !== 'Actions') {
headerCell.addEventListener('click', () => sortTableByColumn(key, headerCell));
}
});
for (const item of data) {
const dataRow = table.insertRow();
columns.forEach((key, index) => {
const dataCell = dataRow.insertCell();
if (key === 'Actions') {
const statusButton = document.createElement('button');
statusButton.textContent = 'Change Status';
statusButton.classList.add('status-button');
statusButton.addEventListener('click', (event) => changeStatus(event, item));
dataCell.appendChild(statusButton);
} else {
if (key === 'creation_date' || key === 'update_date') {
const date = new Date(item[key]._seconds * 1000);
dataCell.textContent = date.toLocaleString();
} else {
dataCell.textContent = item[key];
}
if (key === 'status') {
dataCell.classList.add(`status-${item[key]}`);
}
if (key === 'id') {
dataCell.classList.add('id');
} else if (key === 'collection') {
dataCell.classList.add('collection');
}
}
dataCell.classList.add(`col-${key.toLowerCase().replace('_', '-')}`);
});
}
}
async function changeStatus(event, item) {
const newStatus = (item.status + 1) % 3;
try {
const response = await fetch(updateApiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: item.id, collection: item.collection, status: newStatus })
});
if (response.ok) {
item.status = newStatus;
const row = event.target.closest('tr');
const statusCell = row.querySelector('.col-status');
statusCell.textContent = newStatus;
statusCell.className = `col-status status-${newStatus}`;
} else {
console.error('Error updating status:', response.statusText);
}
} catch (error) {
console.error('Error updating status:', error);
}
}
function sortTableByColumn(column, headerCell) {
const isDateColumn = column === 'creation_date' || column === 'update_date';
const sortedData = [...tableData].sort((a, b) => {
const aValue = isDateColumn ? new Date(a[column]._seconds * 1000) : a[column];
const bValue = isDateColumn ? new Date(b[column]._seconds * 1000) : b[column];
if (aValue < bValue) return -1;
if (aValue > bValue) return 1;
return 0;
});
if (!sortOrder[column] || sortOrder[column] === 'desc') {
sortOrder[column] = 'asc';
} else {
sortOrder[column] = 'desc';
sortedData.reverse();
}
document.querySelectorAll('th').forEach(th => th.classList.remove('sorted-asc', 'sorted-desc'));
headerCell.classList.add(`sorted-${sortOrder[column]}`);
tableData = sortedData;
createTable(tableData);
}
function fetchData() {
fetch(apiUrl)
.then(response => response.json())
.then(data => {
tableData = data;
createTable(tableData);
})
.catch(error => {
console.error('Error fetching data:', error);
});
}
</script>
</body>
</html>
一覧を取得するコード
package json
{
"name": "list-firestore-documents",
"version": "1.0.0",
"description": "Cloud Function to list Firestore documents",
"main": "index.js",
"scripts": {
"deploy": "firebase deploy --only functions"
},
"dependencies": {
"firebase-admin": "^11.9.0",
"firebase-functions": "^4.4.0",
"cors": "^2.8.5"
},
"engines": {
"node": "18"
}
}
indexjs
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true}); // CORSを有効にする
admin.initializeApp();
const db = admin.firestore();
exports.listAllDocuments = functions.https.onRequest((req, res) => {
cors(req, res, async () => { // CORSミドルウェアを適用
try {
const collections = await db.listCollections();
const allDocuments = [];
for (const collection of collections) {
const snapshot = await collection.get();
snapshot.forEach(doc => {
allDocuments.push({ collection: collection.id, id: doc.id, ...doc.data() });
});
}
res.status(200).send(allDocuments);
} catch (error) {
console.error(error);
res.status(500).send('Error listing documents');
}
});
});
アップデート
package json
{
"name": "list-firestore-documents",
"version": "1.0.0",
"description": "Cloud Function to list Firestore documents",
"main": "index.js",
"scripts": {
"deploy": "firebase deploy --only functions"
},
"dependencies": {
"firebase-admin": "^11.9.0",
"firebase-functions": "^4.4.0",
"cors": "^2.8.5"
},
"engines": {
"node": "18"
}
}
indexjs
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true}); // CORSを有効にする
admin.initializeApp();
const db = admin.firestore();
// 新しい関数: データ更新
exports.updateStatus = functions.https.onRequest(async (req, res) => {
cors(req, res, async () => {
try {
const { id, collection, status } = req.body;
const docRef = db.collection(collection).doc(id);
await docRef.update({ status });
res.status(200).send('Status updated successfully');
} catch (error) {
console.error(error);
res.status(500).send('Error updating status');
}
});
});
エントリーポイントは
listAllDocuments
updateStatus
ここから先は
FX自動売買開発入門サンプルコードセット
EA開発者のためのサンプルコード集
よろしければサポートお願いします! いただいたサポートはクリエイターとしての活動費に使わせていただきます!