MindsDB 25.8.2.0__py3-none-any.whl → 25.9.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +5 -45
- mindsdb/api/a2a/__init__.py +52 -0
- mindsdb/api/a2a/agent.py +17 -28
- mindsdb/api/a2a/common/server/server.py +17 -36
- mindsdb/api/a2a/common/server/task_manager.py +14 -28
- mindsdb/api/a2a/common/types.py +3 -4
- mindsdb/api/a2a/task_manager.py +43 -55
- mindsdb/api/a2a/utils.py +63 -0
- mindsdb/api/common/middleware.py +106 -0
- mindsdb/api/http/initialize.py +13 -15
- mindsdb/api/http/namespaces/agents.py +6 -7
- mindsdb/api/http/namespaces/auth.py +6 -14
- mindsdb/api/http/namespaces/config.py +0 -2
- mindsdb/api/http/namespaces/default.py +74 -106
- mindsdb/api/http/start.py +25 -44
- mindsdb/api/litellm/start.py +11 -10
- mindsdb/api/mcp/__init__.py +165 -0
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +33 -64
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +86 -85
- mindsdb/integrations/handlers/crate_handler/crate_handler.py +3 -7
- mindsdb/integrations/handlers/derby_handler/derby_handler.py +32 -34
- mindsdb/integrations/handlers/documentdb_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +12 -13
- mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +45 -44
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +101 -95
- mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +129 -129
- mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +59 -43
- mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +38 -39
- mindsdb/integrations/handlers/informix_handler/informix_handler.py +5 -18
- mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +22 -28
- mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +3 -7
- mindsdb/integrations/handlers/mongodb_handler/mongodb_handler.py +53 -67
- mindsdb/integrations/handlers/mongodb_handler/requirements.txt +1 -0
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_ast.py +43 -68
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py +17 -25
- mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py +10 -16
- mindsdb/integrations/handlers/mongodb_handler/utils/mongodb_render.py +43 -69
- mindsdb/integrations/libs/base.py +1 -1
- mindsdb/interfaces/agents/constants.py +17 -2
- mindsdb/interfaces/agents/langchain_agent.py +83 -18
- mindsdb/interfaces/knowledge_base/controller.py +3 -1
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +7 -1
- mindsdb/interfaces/skills/skill_tool.py +7 -1
- mindsdb/interfaces/skills/sql_agent.py +6 -2
- mindsdb/utilities/config.py +3 -155
- mindsdb/utilities/fs.py +10 -4
- mindsdb/utilities/log.py +0 -25
- mindsdb/utilities/starters.py +0 -39
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/METADATA +265 -263
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/RECORD +54 -98
- mindsdb/api/a2a/__main__.py +0 -144
- mindsdb/api/a2a/run_a2a.py +0 -86
- mindsdb/api/common/check_auth.py +0 -42
- mindsdb/api/http/gunicorn_wrapper.py +0 -17
- mindsdb/api/mcp/start.py +0 -205
- mindsdb/api/mongo/__init__.py +0 -0
- mindsdb/api/mongo/classes/__init__.py +0 -5
- mindsdb/api/mongo/classes/query_sql.py +0 -19
- mindsdb/api/mongo/classes/responder.py +0 -45
- mindsdb/api/mongo/classes/responder_collection.py +0 -34
- mindsdb/api/mongo/classes/scram.py +0 -86
- mindsdb/api/mongo/classes/session.py +0 -23
- mindsdb/api/mongo/functions/__init__.py +0 -19
- mindsdb/api/mongo/responders/__init__.py +0 -73
- mindsdb/api/mongo/responders/add_shard.py +0 -13
- mindsdb/api/mongo/responders/aggregate.py +0 -90
- mindsdb/api/mongo/responders/buildinfo.py +0 -17
- mindsdb/api/mongo/responders/coll_stats.py +0 -63
- mindsdb/api/mongo/responders/company_id.py +0 -25
- mindsdb/api/mongo/responders/connection_status.py +0 -22
- mindsdb/api/mongo/responders/count.py +0 -21
- mindsdb/api/mongo/responders/db_stats.py +0 -32
- mindsdb/api/mongo/responders/delete.py +0 -105
- mindsdb/api/mongo/responders/describe.py +0 -23
- mindsdb/api/mongo/responders/end_sessions.py +0 -13
- mindsdb/api/mongo/responders/find.py +0 -175
- mindsdb/api/mongo/responders/get_cmd_line_opts.py +0 -18
- mindsdb/api/mongo/responders/get_free_monitoring_status.py +0 -14
- mindsdb/api/mongo/responders/get_parameter.py +0 -23
- mindsdb/api/mongo/responders/getlog.py +0 -14
- mindsdb/api/mongo/responders/host_info.py +0 -28
- mindsdb/api/mongo/responders/insert.py +0 -270
- mindsdb/api/mongo/responders/is_master.py +0 -20
- mindsdb/api/mongo/responders/is_master_lower.py +0 -13
- mindsdb/api/mongo/responders/list_collections.py +0 -55
- mindsdb/api/mongo/responders/list_databases.py +0 -37
- mindsdb/api/mongo/responders/list_indexes.py +0 -22
- mindsdb/api/mongo/responders/ping.py +0 -13
- mindsdb/api/mongo/responders/recv_chunk_start.py +0 -13
- mindsdb/api/mongo/responders/replsetgetstatus.py +0 -13
- mindsdb/api/mongo/responders/sasl_continue.py +0 -34
- mindsdb/api/mongo/responders/sasl_start.py +0 -33
- mindsdb/api/mongo/responders/update_range_deletions.py +0 -12
- mindsdb/api/mongo/responders/whatsmyuri.py +0 -18
- mindsdb/api/mongo/server.py +0 -388
- mindsdb/api/mongo/start.py +0 -15
- mindsdb/api/mongo/utilities/__init__.py +0 -0
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.8.2.0.dist-info → mindsdb-25.9.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import gunicorn.app.base
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class StandaloneApplication(gunicorn.app.base.BaseApplication):
|
|
5
|
-
def __init__(self, app, options=None):
|
|
6
|
-
self.options = options or {}
|
|
7
|
-
self.application = app
|
|
8
|
-
super().__init__()
|
|
9
|
-
|
|
10
|
-
def load_config(self):
|
|
11
|
-
config = {key: value for key, value in self.options.items()
|
|
12
|
-
if key in self.cfg.settings and value is not None}
|
|
13
|
-
for key, value in config.items():
|
|
14
|
-
self.cfg.set(key.lower(), value)
|
|
15
|
-
|
|
16
|
-
def load(self):
|
|
17
|
-
return self.application
|
mindsdb/api/mcp/start.py
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Any
|
|
3
|
-
from textwrap import dedent
|
|
4
|
-
from contextlib import asynccontextmanager
|
|
5
|
-
from collections.abc import AsyncIterator
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
|
|
8
|
-
import uvicorn
|
|
9
|
-
import anyio
|
|
10
|
-
from mcp.server.fastmcp import FastMCP
|
|
11
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
12
|
-
from starlette.requests import Request
|
|
13
|
-
from starlette.responses import Response
|
|
14
|
-
|
|
15
|
-
from mindsdb.api.mysql.mysql_proxy.classes.fake_mysql_proxy import FakeMysqlProxy
|
|
16
|
-
from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE as SQL_RESPONSE_TYPE
|
|
17
|
-
from mindsdb.utilities import log
|
|
18
|
-
from mindsdb.utilities.log import get_uvicorn_logging_config
|
|
19
|
-
from mindsdb.utilities.config import Config
|
|
20
|
-
from mindsdb.interfaces.storage import db
|
|
21
|
-
|
|
22
|
-
logger = log.getLogger(__name__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@dataclass
|
|
26
|
-
class AppContext:
|
|
27
|
-
db: Any
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@asynccontextmanager
|
|
31
|
-
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
32
|
-
"""Manage application lifecycle with type-safe context"""
|
|
33
|
-
# Initialize on startup
|
|
34
|
-
db.init()
|
|
35
|
-
try:
|
|
36
|
-
yield AppContext(db=db)
|
|
37
|
-
finally:
|
|
38
|
-
# TODO: We need better way to handle this in storage/db.py
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# Configure server with lifespan
|
|
43
|
-
mcp = FastMCP(
|
|
44
|
-
"MindsDB",
|
|
45
|
-
lifespan=app_lifespan,
|
|
46
|
-
dependencies=["mindsdb"], # Add any additional dependencies
|
|
47
|
-
)
|
|
48
|
-
# MCP Queries
|
|
49
|
-
LISTING_QUERY = "SHOW DATABASES"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
query_tool_description = dedent("""\
|
|
53
|
-
Executes a SQL query against MindsDB.
|
|
54
|
-
|
|
55
|
-
A database must be specified either in the `context` parameter or directly in the query string (e.g., `SELECT * FROM my_database.my_table`). Queries like `SELECT * FROM my_table` will fail without a `context`.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
query (str): The SQL query to execute.
|
|
59
|
-
context (dict, optional): The default database context. For example, `{"db": "my_postgres"}`.
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
A dictionary describing the result.
|
|
63
|
-
- For a successful query with no data to return (e.g., an `UPDATE` statement), the response is `{"type": "ok"}`.
|
|
64
|
-
- If the query returns tabular data, the response is a dictionary containing `data` (a list of rows) and `column_names` (a list of column names). For example: `{"type": "table", "data": [[1, "a"], [2, "b"]], "column_names": ["column_a", "column_b"]}`.
|
|
65
|
-
- In case of an error, a response is `{"type": "error", "error_message": "the error message"}`.
|
|
66
|
-
""")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@mcp.tool(name="query", description=query_tool_description)
|
|
70
|
-
def query(query: str, context: dict | None = None) -> dict[str, Any]:
|
|
71
|
-
"""Execute a SQL query against MindsDB
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
query: The SQL query to execute
|
|
75
|
-
context: Optional context parameters for the query
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
Dict containing the query results or error information
|
|
79
|
-
"""
|
|
80
|
-
|
|
81
|
-
if context is None:
|
|
82
|
-
context = {}
|
|
83
|
-
|
|
84
|
-
logger.debug(f"Incoming MCP query: {query}")
|
|
85
|
-
|
|
86
|
-
mysql_proxy = FakeMysqlProxy()
|
|
87
|
-
mysql_proxy.set_context(context)
|
|
88
|
-
|
|
89
|
-
try:
|
|
90
|
-
result = mysql_proxy.process_query(query)
|
|
91
|
-
|
|
92
|
-
if result.type == SQL_RESPONSE_TYPE.OK:
|
|
93
|
-
return {"type": SQL_RESPONSE_TYPE.OK}
|
|
94
|
-
|
|
95
|
-
if result.type == SQL_RESPONSE_TYPE.TABLE:
|
|
96
|
-
return {
|
|
97
|
-
"type": SQL_RESPONSE_TYPE.TABLE,
|
|
98
|
-
"data": result.result_set.to_lists(json_types=True),
|
|
99
|
-
"column_names": [column.alias or column.name for column in result.result_set.columns],
|
|
100
|
-
}
|
|
101
|
-
else:
|
|
102
|
-
return {"type": SQL_RESPONSE_TYPE.ERROR, "error_code": 0, "error_message": "Unknown response type"}
|
|
103
|
-
|
|
104
|
-
except Exception as e:
|
|
105
|
-
logger.error(f"Error processing query: {str(e)}")
|
|
106
|
-
return {"type": SQL_RESPONSE_TYPE.ERROR, "error_code": 0, "error_message": str(e)}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
list_databases_tool_description = (
|
|
110
|
-
"Returns a list of all database connections currently available in MindsDB. "
|
|
111
|
-
+ "The tool takes no parameters and responds with a list of database names, "
|
|
112
|
-
+ 'for example: ["my_postgres", "my_mysql", "test_db"].'
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@mcp.tool(name="list_databases", description=list_databases_tool_description)
|
|
117
|
-
def list_databases() -> list[str]:
|
|
118
|
-
"""
|
|
119
|
-
List all databases in MindsDB
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
list[str]: list of databases
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
mysql_proxy = FakeMysqlProxy()
|
|
126
|
-
|
|
127
|
-
try:
|
|
128
|
-
result = mysql_proxy.process_query(LISTING_QUERY)
|
|
129
|
-
if result.type == SQL_RESPONSE_TYPE.ERROR:
|
|
130
|
-
return {
|
|
131
|
-
"type": "error",
|
|
132
|
-
"error_code": result.error_code,
|
|
133
|
-
"error_message": result.error_message,
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
elif result.type == SQL_RESPONSE_TYPE.OK:
|
|
137
|
-
return {"type": "ok"}
|
|
138
|
-
|
|
139
|
-
elif result.type == SQL_RESPONSE_TYPE.TABLE:
|
|
140
|
-
data = result.result_set.to_lists(json_types=True)
|
|
141
|
-
data = [val[0] for val in data]
|
|
142
|
-
return data
|
|
143
|
-
|
|
144
|
-
except Exception as e:
|
|
145
|
-
return {
|
|
146
|
-
"type": "error",
|
|
147
|
-
"error_code": 0,
|
|
148
|
-
"error_message": str(e),
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
class CustomAuthMiddleware(BaseHTTPMiddleware):
|
|
153
|
-
"""Custom middleware to handle authentication basing on header 'Authorization'"""
|
|
154
|
-
|
|
155
|
-
async def dispatch(self, request: Request, call_next):
|
|
156
|
-
mcp_access_token = os.environ.get("MINDSDB_MCP_ACCESS_TOKEN")
|
|
157
|
-
if mcp_access_token is not None:
|
|
158
|
-
auth_token = request.headers.get("Authorization", "").partition("Bearer ")[-1]
|
|
159
|
-
if mcp_access_token != auth_token:
|
|
160
|
-
return Response(status_code=401, content="Unauthorized", media_type="text/plain")
|
|
161
|
-
|
|
162
|
-
response = await call_next(request)
|
|
163
|
-
|
|
164
|
-
return response
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
async def run_sse_async() -> None:
|
|
168
|
-
"""Run the server using SSE transport."""
|
|
169
|
-
starlette_app = mcp.sse_app()
|
|
170
|
-
starlette_app.add_middleware(CustomAuthMiddleware)
|
|
171
|
-
|
|
172
|
-
config = uvicorn.Config(
|
|
173
|
-
starlette_app,
|
|
174
|
-
host=mcp.settings.host,
|
|
175
|
-
port=mcp.settings.port,
|
|
176
|
-
log_level=mcp.settings.log_level.lower(),
|
|
177
|
-
log_config=get_uvicorn_logging_config("uvicorn_mcp"),
|
|
178
|
-
)
|
|
179
|
-
server = uvicorn.Server(config)
|
|
180
|
-
await server.serve()
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def start(*args, **kwargs):
|
|
184
|
-
"""Start the MCP server
|
|
185
|
-
Args:
|
|
186
|
-
host (str): Host to bind to
|
|
187
|
-
port (int): Port to listen on
|
|
188
|
-
"""
|
|
189
|
-
config = Config()
|
|
190
|
-
port = int(config["api"].get("mcp", {}).get("port", 47337))
|
|
191
|
-
host = config["api"].get("mcp", {}).get("host", "127.0.0.1")
|
|
192
|
-
|
|
193
|
-
logger.info(f"Starting MCP server on {host}:{port}")
|
|
194
|
-
mcp.settings.host = host
|
|
195
|
-
mcp.settings.port = port
|
|
196
|
-
|
|
197
|
-
try:
|
|
198
|
-
anyio.run(run_sse_async)
|
|
199
|
-
except Exception as e:
|
|
200
|
-
logger.error(f"Error starting MCP server: {str(e)}")
|
|
201
|
-
raise
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if __name__ == "__main__":
|
|
205
|
-
start()
|
mindsdb/api/mongo/__init__.py
DELETED
|
File without changes
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from mindsdb.api.executor.controllers import SessionController
|
|
2
|
-
from mindsdb.api.executor.command_executor import ExecuteCommands
|
|
3
|
-
from mindsdb.utilities.config import config
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def run_sql_command(request_env, ast_query):
|
|
7
|
-
sql_session = SessionController()
|
|
8
|
-
sql_session.database = request_env.get('database', config.get('default_project'))
|
|
9
|
-
|
|
10
|
-
command_executor = ExecuteCommands(sql_session)
|
|
11
|
-
ret = command_executor.execute_command(ast_query)
|
|
12
|
-
if ret.error_code is not None:
|
|
13
|
-
raise Exception(ret.error_message)
|
|
14
|
-
|
|
15
|
-
if ret.data is None:
|
|
16
|
-
# return no data
|
|
17
|
-
return []
|
|
18
|
-
|
|
19
|
-
return list(ret.data.get_records())
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
class Responder():
|
|
2
|
-
def __init__(self, when=None, result=None):
|
|
3
|
-
if when is not None:
|
|
4
|
-
self.when = when
|
|
5
|
-
if result is not None:
|
|
6
|
-
self.result = result
|
|
7
|
-
if not hasattr(self, 'when') or (not isinstance(self.when, dict) and not callable(self.when)):
|
|
8
|
-
raise ValueError("Responder attr 'when' must be dict or function.")
|
|
9
|
-
if not hasattr(self, 'result') or (not isinstance(self.result, dict) and not callable(self.result)):
|
|
10
|
-
raise ValueError("Responder attr 'result' must be dict or function.")
|
|
11
|
-
|
|
12
|
-
def match(self, query):
|
|
13
|
-
""" check, if this 'responder' can be used to answer or current request
|
|
14
|
-
|
|
15
|
-
query (dict): request document
|
|
16
|
-
|
|
17
|
-
return bool
|
|
18
|
-
"""
|
|
19
|
-
if isinstance(self.when, dict):
|
|
20
|
-
for key, value in self.when.items():
|
|
21
|
-
if key not in query:
|
|
22
|
-
return False
|
|
23
|
-
if callable(value):
|
|
24
|
-
if not value(query[key]):
|
|
25
|
-
return False
|
|
26
|
-
elif value != query[key]:
|
|
27
|
-
return False
|
|
28
|
-
return True
|
|
29
|
-
else:
|
|
30
|
-
return self.when(query)
|
|
31
|
-
|
|
32
|
-
def handle(self, query, args, env, session):
|
|
33
|
-
""" making answer based on params:
|
|
34
|
-
|
|
35
|
-
query (dict): document(s) from request
|
|
36
|
-
args (dict): all other significant information from request: flags, collection name, rows to return, etc
|
|
37
|
-
env (dict): config, model_controller instance, and other mindsdb related stuff
|
|
38
|
-
session (object): current session
|
|
39
|
-
|
|
40
|
-
returns documents as dict or list of dicts
|
|
41
|
-
"""
|
|
42
|
-
if isinstance(self.result, dict):
|
|
43
|
-
return self.result
|
|
44
|
-
else:
|
|
45
|
-
return self.result(query, args, env, session)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from .responder import Responder
|
|
2
|
-
from mindsdb.utilities import log
|
|
3
|
-
|
|
4
|
-
logger = log.getLogger(__name__)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class RespondersCollection():
|
|
8
|
-
def __init__(self):
|
|
9
|
-
self.responders = []
|
|
10
|
-
|
|
11
|
-
def find_match(self, query):
|
|
12
|
-
for r in self.responders:
|
|
13
|
-
if r.match(query):
|
|
14
|
-
return r
|
|
15
|
-
|
|
16
|
-
msg = f'Is not responder for query: {query}'
|
|
17
|
-
|
|
18
|
-
class ErrorResponder(Responder):
|
|
19
|
-
when = {}
|
|
20
|
-
|
|
21
|
-
result = {
|
|
22
|
-
"ok": 0.0,
|
|
23
|
-
"errmsg": msg,
|
|
24
|
-
"code": 59,
|
|
25
|
-
"codeName": "CommandNotFound"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
logger.error(msg)
|
|
29
|
-
return ErrorResponder()
|
|
30
|
-
|
|
31
|
-
def add(self, when, result):
|
|
32
|
-
self.responders.append(
|
|
33
|
-
Responder(when, result)
|
|
34
|
-
)
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# https://stackoverflow.com/questions/29298346/xmpp-sasl-scram-sha1-authentication
|
|
2
|
-
# https://tools.ietf.org/html/rfc5802
|
|
3
|
-
# https://tools.ietf.org/html/rfc7677
|
|
4
|
-
|
|
5
|
-
import base64
|
|
6
|
-
import hashlib
|
|
7
|
-
import hmac
|
|
8
|
-
import os
|
|
9
|
-
|
|
10
|
-
from pymongo.auth import _password_digest, _xor, saslprep
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Scram():
|
|
14
|
-
''' implementation of server-side SCRAM-SHA-1 and SCRAM-SHA-256 auth for mongodb
|
|
15
|
-
'''
|
|
16
|
-
|
|
17
|
-
def __init__(self, method='sha1', get_salted_password=None):
|
|
18
|
-
self.get_salted_password = get_salted_password
|
|
19
|
-
self.snonce = base64.b64encode(os.urandom(24)).decode()
|
|
20
|
-
self.iterations = 4096
|
|
21
|
-
self.messages = []
|
|
22
|
-
|
|
23
|
-
if method == 'sha1':
|
|
24
|
-
self.method_str = 'sha1'
|
|
25
|
-
self.method_func = hashlib.sha1
|
|
26
|
-
self.salt = base64.b64encode(os.urandom(16))
|
|
27
|
-
elif method == 'sha256':
|
|
28
|
-
self.method_str = 'sha256'
|
|
29
|
-
self.method_func = hashlib.sha256
|
|
30
|
-
self.salt = base64.b64encode(os.urandom(28))
|
|
31
|
-
|
|
32
|
-
def process_client_first_message(self, payload):
|
|
33
|
-
payload = payload[3:]
|
|
34
|
-
payload_parts = self._split_payload(payload)
|
|
35
|
-
self.client_user = payload_parts['n']
|
|
36
|
-
|
|
37
|
-
if self.get_salted_password is not None:
|
|
38
|
-
salt_bytes, self.salted_password = self.get_salted_password(self.client_user, self.method_str)
|
|
39
|
-
self.salt = base64.b64encode(salt_bytes)
|
|
40
|
-
else:
|
|
41
|
-
self.salted_password = self.salt_password()
|
|
42
|
-
|
|
43
|
-
self.unonce = payload_parts['r']
|
|
44
|
-
self.messages.append(payload)
|
|
45
|
-
|
|
46
|
-
responce_msg = f"r={self.unonce}{self.snonce},s={self.salt.decode()},i={self.iterations}"
|
|
47
|
-
self.messages.append(responce_msg)
|
|
48
|
-
return responce_msg
|
|
49
|
-
|
|
50
|
-
def process_client_second_message(self, payload):
|
|
51
|
-
self.messages.append(payload[:payload.rfind(',p=')]) # without 'p' part
|
|
52
|
-
|
|
53
|
-
messages = ','.join(self.messages)
|
|
54
|
-
server_key = self._hmac(self.salted_password, b'Server Key')
|
|
55
|
-
server_signature = self._hmac(server_key, messages.encode('utf-8'))
|
|
56
|
-
|
|
57
|
-
client_key = self._hmac(self.salted_password, b'Client Key')
|
|
58
|
-
stored_key = self.method_func(client_key).digest()
|
|
59
|
-
client_signature = self._hmac(stored_key, messages.encode('utf-8'))
|
|
60
|
-
expected_client_proof = base64.b64encode(_xor(client_key, client_signature)).decode()
|
|
61
|
-
|
|
62
|
-
income_client_proof = payload[payload.rfind(',p=') + 3:]
|
|
63
|
-
|
|
64
|
-
if expected_client_proof != income_client_proof:
|
|
65
|
-
raise Exception('wrong password')
|
|
66
|
-
|
|
67
|
-
return f'v={base64.b64encode(server_signature).decode()}'
|
|
68
|
-
|
|
69
|
-
def _hmac(self, key, msg):
|
|
70
|
-
return hmac.new(key, msg, digestmod=self.method_func).digest()
|
|
71
|
-
|
|
72
|
-
def salt_password(self, user, password):
|
|
73
|
-
if self.method_str == 'sha1':
|
|
74
|
-
password = _password_digest(user, password).encode("utf-8")
|
|
75
|
-
elif self.method_str == 'sha256':
|
|
76
|
-
password = saslprep(password).encode("utf-8")
|
|
77
|
-
|
|
78
|
-
return hashlib.pbkdf2_hmac(
|
|
79
|
-
self.method_str, password, base64.b64decode(self.salt), self.iterations
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
def _split_payload(self, payload):
|
|
83
|
-
parts = {}
|
|
84
|
-
for part in [x for x in payload.split(',') if len(x) > 0]:
|
|
85
|
-
parts[part[0]] = part[2:]
|
|
86
|
-
return parts
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
|
|
3
|
-
from mindsdb.api.mongo.classes.scram import Scram
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Session():
|
|
7
|
-
def __init__(self, server_mindsdb_env):
|
|
8
|
-
self.config = server_mindsdb_env['config']
|
|
9
|
-
self.mindsdb_env = {'company_id': None}
|
|
10
|
-
self.mindsdb_env.update(server_mindsdb_env)
|
|
11
|
-
|
|
12
|
-
def init_scram(self, method):
|
|
13
|
-
self.scram = Scram(method=method, get_salted_password=self.get_salted_password)
|
|
14
|
-
|
|
15
|
-
def get_salted_password(self, username, method=None):
|
|
16
|
-
real_user = self.config['auth'].get('username', '')
|
|
17
|
-
password = self.config['auth'].get('password', '')
|
|
18
|
-
if username != real_user:
|
|
19
|
-
raise Exception(f'Wrong username {username}')
|
|
20
|
-
|
|
21
|
-
salted_password = self.scram.salt_password(real_user, password)
|
|
22
|
-
|
|
23
|
-
return base64.b64decode(self.scram.salt), salted_password
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import bson
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def is_true(val):
|
|
5
|
-
return bool(val) is True
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def is_false(val):
|
|
9
|
-
return bool(val) is False
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def int_to_objectid(n):
|
|
13
|
-
s = str(n)
|
|
14
|
-
s = '0' * (24 - len(s)) + s
|
|
15
|
-
return bson.ObjectId(s)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def objectid_to_int(obj):
|
|
19
|
-
return int(str(obj))
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
from .whatsmyuri import responder as responder_whatsmyuri
|
|
2
|
-
from .buildinfo import responder as responder_buildinfo
|
|
3
|
-
from .is_master import responder as responder_is_master
|
|
4
|
-
from .is_master_lower import responder as responder_is_master_lower
|
|
5
|
-
from .replsetgetstatus import responder as responder_replsetgetstatus
|
|
6
|
-
from .getlog import responder as responder_getlog
|
|
7
|
-
from .add_shard import responder as responder_add_shard
|
|
8
|
-
from .update_range_deletions import responder as responder_update_range_deletions
|
|
9
|
-
from .recv_chunk_start import responder as responder_recv_chunk_start
|
|
10
|
-
from .connection_status import responder as responder_connection_status
|
|
11
|
-
from .get_cmd_line_opts import responder as responder_get_cmd_line_opts
|
|
12
|
-
from .host_info import responder as responder_host_info
|
|
13
|
-
from .db_stats import responder as responder_db_stats
|
|
14
|
-
from .coll_stats import responder as responder_coll_stats
|
|
15
|
-
from .count import responder as responder_count
|
|
16
|
-
from .aggregate import responder as responder_aggregate
|
|
17
|
-
from .get_free_monitoring_status import responder as responder_get_free_monitoring_status
|
|
18
|
-
from .end_sessions import responder as responder_end_sessions
|
|
19
|
-
from .ping import responder as responder_ping
|
|
20
|
-
from .get_parameter import responder as responder_get_parameter
|
|
21
|
-
|
|
22
|
-
from .list_indexes import responder as responder_list_indexes
|
|
23
|
-
from .list_collections import responder as responder_list_collections
|
|
24
|
-
from .list_databases import responder as responder_list_databases
|
|
25
|
-
|
|
26
|
-
from .find import responder as responder_find
|
|
27
|
-
from .insert import responder as responder_insert
|
|
28
|
-
from .delete import responder as responder_delete
|
|
29
|
-
from .describe import responder as responder_describe
|
|
30
|
-
|
|
31
|
-
from .sasl_start import responder as sasl_start
|
|
32
|
-
from .sasl_continue import responder as sasl_continue
|
|
33
|
-
|
|
34
|
-
from .company_id import responder as responder_company_id
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
responders = [
|
|
38
|
-
# service queries
|
|
39
|
-
responder_whatsmyuri,
|
|
40
|
-
responder_buildinfo,
|
|
41
|
-
responder_is_master,
|
|
42
|
-
responder_is_master_lower,
|
|
43
|
-
responder_replsetgetstatus,
|
|
44
|
-
responder_getlog,
|
|
45
|
-
responder_add_shard, # 4.4
|
|
46
|
-
responder_update_range_deletions, # 4.4
|
|
47
|
-
responder_recv_chunk_start, # 4.4
|
|
48
|
-
responder_connection_status,
|
|
49
|
-
responder_get_cmd_line_opts,
|
|
50
|
-
responder_host_info,
|
|
51
|
-
responder_db_stats,
|
|
52
|
-
responder_coll_stats,
|
|
53
|
-
responder_count,
|
|
54
|
-
responder_aggregate,
|
|
55
|
-
responder_get_free_monitoring_status,
|
|
56
|
-
responder_end_sessions,
|
|
57
|
-
responder_ping,
|
|
58
|
-
responder_get_parameter,
|
|
59
|
-
|
|
60
|
-
# user queries
|
|
61
|
-
responder_list_indexes,
|
|
62
|
-
responder_list_collections,
|
|
63
|
-
responder_list_databases,
|
|
64
|
-
responder_find,
|
|
65
|
-
responder_insert,
|
|
66
|
-
responder_delete,
|
|
67
|
-
responder_describe,
|
|
68
|
-
# auth
|
|
69
|
-
sasl_start,
|
|
70
|
-
sasl_continue,
|
|
71
|
-
# cloud
|
|
72
|
-
responder_company_id
|
|
73
|
-
]
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from bson.int64 import Int64
|
|
2
|
-
|
|
3
|
-
from mindsdb_sql_parser.ast import Identifier, Insert, CreateTable
|
|
4
|
-
|
|
5
|
-
from mindsdb.api.mongo.classes import Responder
|
|
6
|
-
import mindsdb.api.mongo.functions as helpers
|
|
7
|
-
from mindsdb.api.mongo.responders.find import find_to_ast
|
|
8
|
-
from mindsdb.api.mongo.classes.query_sql import run_sql_command
|
|
9
|
-
from mindsdb.utilities.config import config
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def aggregate_to_ast(query, database):
|
|
13
|
-
collection = query['aggregate']
|
|
14
|
-
|
|
15
|
-
save_table = None
|
|
16
|
-
is_append = False
|
|
17
|
-
first_step = query['pipeline'][0]
|
|
18
|
-
if '$match' in first_step:
|
|
19
|
-
# convert to find
|
|
20
|
-
find_query = {
|
|
21
|
-
'find': collection,
|
|
22
|
-
'filter': first_step['$match'],
|
|
23
|
-
}
|
|
24
|
-
for step in query['pipeline'][1:]:
|
|
25
|
-
if '$project' in step:
|
|
26
|
-
find_query['projection'] = step['$project']
|
|
27
|
-
if '$sort' in step:
|
|
28
|
-
find_query['sort'] = step['$sort']
|
|
29
|
-
if '$skip' in step:
|
|
30
|
-
find_query['skip'] = step['$skip']
|
|
31
|
-
if '$limit' in step:
|
|
32
|
-
find_query['limit'] = step['$limit']
|
|
33
|
-
|
|
34
|
-
if '$out' in step:
|
|
35
|
-
target = step['$out']
|
|
36
|
-
if isinstance(target, str):
|
|
37
|
-
save_table = Identifier(target)
|
|
38
|
-
else:
|
|
39
|
-
save_table = Identifier(parts=[target['db'], target['coll']])
|
|
40
|
-
if target.get('append'):
|
|
41
|
-
is_append = True
|
|
42
|
-
|
|
43
|
-
# TODO implement group
|
|
44
|
-
ast_query = find_to_ast(find_query, database)
|
|
45
|
-
if save_table is not None:
|
|
46
|
-
if is_append:
|
|
47
|
-
ast_query = Insert(save_table, from_select=ast_query)
|
|
48
|
-
else:
|
|
49
|
-
ast_query = CreateTable(save_table, from_select=ast_query, is_replace=True)
|
|
50
|
-
|
|
51
|
-
else:
|
|
52
|
-
raise NotImplementedError
|
|
53
|
-
|
|
54
|
-
return ast_query
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class Responce(Responder):
|
|
58
|
-
when = {'aggregate': helpers.is_true}
|
|
59
|
-
|
|
60
|
-
def result(self, query, request_env, mindsdb_env, session):
|
|
61
|
-
db = query['$db']
|
|
62
|
-
collection = query['aggregate']
|
|
63
|
-
|
|
64
|
-
first_step = query['pipeline'][0]
|
|
65
|
-
if '$match' in first_step:
|
|
66
|
-
ast_query = aggregate_to_ast(query, request_env.get('database', config.get('default_project')))
|
|
67
|
-
|
|
68
|
-
data = run_sql_command(request_env, ast_query)
|
|
69
|
-
|
|
70
|
-
elif '$collStats' in first_step:
|
|
71
|
-
raise ValueError(
|
|
72
|
-
"To describe model use:"
|
|
73
|
-
" db.runCommand({describe: 'model_name.attribute'})"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
else:
|
|
77
|
-
raise NotImplementedError
|
|
78
|
-
|
|
79
|
-
cursor = {
|
|
80
|
-
'id': Int64(0),
|
|
81
|
-
'ns': f"{db}.$cmd.{collection}",
|
|
82
|
-
'firstBatch': data
|
|
83
|
-
}
|
|
84
|
-
return {
|
|
85
|
-
'cursor': cursor,
|
|
86
|
-
'ok': 1
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
responder = Responce()
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from mindsdb.api.mongo.classes import Responder
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Responce(Responder):
|
|
5
|
-
def when(self, query):
|
|
6
|
-
return (
|
|
7
|
-
'buildinfo' in query or 'buildInfo' in query
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
result = {
|
|
11
|
-
'version': '3.6.8',
|
|
12
|
-
'versionArray': [3, 6, 8, 0],
|
|
13
|
-
'ok': 1
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
responder = Responce()
|