MindsDB 25.8.3.0__py3-none-any.whl → 25.9.1.1__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.

Files changed (109) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +3 -45
  3. mindsdb/api/a2a/__init__.py +52 -0
  4. mindsdb/api/a2a/agent.py +11 -12
  5. mindsdb/api/a2a/common/server/server.py +17 -36
  6. mindsdb/api/a2a/common/server/task_manager.py +14 -28
  7. mindsdb/api/a2a/task_manager.py +20 -21
  8. mindsdb/api/a2a/utils.py +1 -1
  9. mindsdb/api/common/middleware.py +106 -0
  10. mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +466 -18
  11. mindsdb/api/executor/utilities/sql.py +9 -31
  12. mindsdb/api/http/initialize.py +34 -43
  13. mindsdb/api/http/namespaces/auth.py +6 -14
  14. mindsdb/api/http/namespaces/config.py +0 -2
  15. mindsdb/api/http/namespaces/default.py +74 -106
  16. mindsdb/api/http/namespaces/file.py +9 -3
  17. mindsdb/api/http/namespaces/handlers.py +77 -87
  18. mindsdb/api/http/start.py +29 -47
  19. mindsdb/api/litellm/start.py +11 -10
  20. mindsdb/api/mcp/__init__.py +165 -0
  21. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +33 -64
  22. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +86 -85
  23. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  25. mindsdb/integrations/handlers/crate_handler/crate_handler.py +3 -7
  26. mindsdb/integrations/handlers/derby_handler/derby_handler.py +32 -34
  27. mindsdb/integrations/handlers/documentdb_handler/requirements.txt +1 -0
  28. mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +12 -13
  29. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  30. mindsdb/integrations/handlers/google_books_handler/google_books_handler.py +45 -44
  31. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +101 -95
  32. mindsdb/integrations/handlers/google_content_shopping_handler/google_content_shopping_handler.py +129 -129
  33. mindsdb/integrations/handlers/google_fit_handler/google_fit_handler.py +59 -43
  34. mindsdb/integrations/handlers/google_search_handler/google_search_handler.py +38 -39
  35. mindsdb/integrations/handlers/informix_handler/informix_handler.py +5 -18
  36. mindsdb/integrations/handlers/lightfm_handler/requirements.txt +1 -1
  37. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  38. mindsdb/integrations/handlers/maxdb_handler/maxdb_handler.py +22 -28
  39. mindsdb/integrations/handlers/monetdb_handler/monetdb_handler.py +3 -7
  40. mindsdb/integrations/handlers/mongodb_handler/mongodb_handler.py +53 -67
  41. mindsdb/integrations/handlers/mongodb_handler/requirements.txt +1 -0
  42. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_ast.py +43 -68
  43. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_parser.py +17 -25
  44. mindsdb/{api/mongo/utilities → integrations/handlers/mongodb_handler/utils}/mongodb_query.py +10 -16
  45. mindsdb/integrations/handlers/mongodb_handler/utils/mongodb_render.py +43 -69
  46. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  47. mindsdb/integrations/libs/base.py +1 -1
  48. mindsdb/integrations/libs/llm/config.py +15 -0
  49. mindsdb/integrations/libs/llm/utils.py +15 -0
  50. mindsdb/interfaces/agents/constants.py +1 -0
  51. mindsdb/interfaces/agents/langchain_agent.py +4 -0
  52. mindsdb/interfaces/agents/providers.py +20 -0
  53. mindsdb/interfaces/knowledge_base/controller.py +25 -7
  54. mindsdb/utilities/config.py +15 -158
  55. mindsdb/utilities/log.py +0 -25
  56. mindsdb/utilities/render/sqlalchemy_render.py +7 -1
  57. mindsdb/utilities/starters.py +0 -39
  58. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/METADATA +269 -267
  59. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/RECORD +62 -105
  60. mindsdb/api/a2a/__main__.py +0 -144
  61. mindsdb/api/a2a/run_a2a.py +0 -86
  62. mindsdb/api/common/check_auth.py +0 -42
  63. mindsdb/api/http/gunicorn_wrapper.py +0 -17
  64. mindsdb/api/mcp/start.py +0 -205
  65. mindsdb/api/mongo/__init__.py +0 -0
  66. mindsdb/api/mongo/classes/__init__.py +0 -5
  67. mindsdb/api/mongo/classes/query_sql.py +0 -19
  68. mindsdb/api/mongo/classes/responder.py +0 -45
  69. mindsdb/api/mongo/classes/responder_collection.py +0 -34
  70. mindsdb/api/mongo/classes/scram.py +0 -86
  71. mindsdb/api/mongo/classes/session.py +0 -23
  72. mindsdb/api/mongo/functions/__init__.py +0 -19
  73. mindsdb/api/mongo/responders/__init__.py +0 -73
  74. mindsdb/api/mongo/responders/add_shard.py +0 -13
  75. mindsdb/api/mongo/responders/aggregate.py +0 -90
  76. mindsdb/api/mongo/responders/buildinfo.py +0 -17
  77. mindsdb/api/mongo/responders/coll_stats.py +0 -63
  78. mindsdb/api/mongo/responders/company_id.py +0 -25
  79. mindsdb/api/mongo/responders/connection_status.py +0 -22
  80. mindsdb/api/mongo/responders/count.py +0 -21
  81. mindsdb/api/mongo/responders/db_stats.py +0 -32
  82. mindsdb/api/mongo/responders/delete.py +0 -105
  83. mindsdb/api/mongo/responders/describe.py +0 -23
  84. mindsdb/api/mongo/responders/end_sessions.py +0 -13
  85. mindsdb/api/mongo/responders/find.py +0 -175
  86. mindsdb/api/mongo/responders/get_cmd_line_opts.py +0 -18
  87. mindsdb/api/mongo/responders/get_free_monitoring_status.py +0 -14
  88. mindsdb/api/mongo/responders/get_parameter.py +0 -23
  89. mindsdb/api/mongo/responders/getlog.py +0 -14
  90. mindsdb/api/mongo/responders/host_info.py +0 -28
  91. mindsdb/api/mongo/responders/insert.py +0 -270
  92. mindsdb/api/mongo/responders/is_master.py +0 -20
  93. mindsdb/api/mongo/responders/is_master_lower.py +0 -13
  94. mindsdb/api/mongo/responders/list_collections.py +0 -55
  95. mindsdb/api/mongo/responders/list_databases.py +0 -37
  96. mindsdb/api/mongo/responders/list_indexes.py +0 -22
  97. mindsdb/api/mongo/responders/ping.py +0 -13
  98. mindsdb/api/mongo/responders/recv_chunk_start.py +0 -13
  99. mindsdb/api/mongo/responders/replsetgetstatus.py +0 -13
  100. mindsdb/api/mongo/responders/sasl_continue.py +0 -34
  101. mindsdb/api/mongo/responders/sasl_start.py +0 -33
  102. mindsdb/api/mongo/responders/update_range_deletions.py +0 -12
  103. mindsdb/api/mongo/responders/whatsmyuri.py +0 -18
  104. mindsdb/api/mongo/server.py +0 -388
  105. mindsdb/api/mongo/start.py +0 -15
  106. mindsdb/api/mongo/utilities/__init__.py +0 -0
  107. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/WHEEL +0 -0
  108. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/licenses/LICENSE +0 -0
  109. {mindsdb-25.8.3.0.dist-info → mindsdb-25.9.1.1.dist-info}/top_level.txt +0 -0
@@ -14,19 +14,7 @@ from mindsdb.utilities.exception import format_db_error_message
14
14
  from mindsdb.utilities.functions import resolve_table_identifier, resolve_model_identifier
15
15
  from mindsdb.utilities.json_encoder import CustomJSONEncoder
16
16
  from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
17
- from mindsdb.api.executor.utilities.mysql_to_duckdb_functions import (
18
- adapt_char_fn,
19
- adapt_locate_fn,
20
- adapt_unhex_fn,
21
- adapt_format_fn,
22
- adapt_sha2_fn,
23
- adapt_length_fn,
24
- adapt_regexp_substr_fn,
25
- adapt_substring_index_fn,
26
- adapt_curtime_fn,
27
- adapt_timestampdiff_fn,
28
- adapt_extract_fn,
29
- )
17
+ from mindsdb.api.executor.utilities.mysql_to_duckdb_functions import mysql_to_duckdb_fnc
30
18
 
31
19
  logger = log.getLogger(__name__)
32
20
 
@@ -196,25 +184,15 @@ def query_df(df, query, session=None):
196
184
  node.parts = [node.parts[-1]]
197
185
  return node
198
186
  if isinstance(node, Function):
199
- fnc_name = node.op.lower()
200
-
201
- mysql_to_duck_fn_map = {
202
- "char": adapt_char_fn,
203
- "locate": adapt_locate_fn,
204
- "insrt": adapt_locate_fn,
205
- "unhex": adapt_unhex_fn,
206
- "format": adapt_format_fn,
207
- "sha2": adapt_sha2_fn,
208
- "length": adapt_length_fn,
209
- "regexp_substr": adapt_regexp_substr_fn,
210
- "substring_index": adapt_substring_index_fn,
211
- "curtime": adapt_curtime_fn,
212
- "timestampdiff": adapt_timestampdiff_fn,
213
- "extract": adapt_extract_fn,
214
- }
215
- if fnc_name in mysql_to_duck_fn_map:
216
- return mysql_to_duck_fn_map[fnc_name](node)
187
+ fnc = mysql_to_duckdb_fnc(node)
188
+ if fnc is not None:
189
+ node2 = fnc(node)
190
+ if node2 is not None:
191
+ # copy alias
192
+ node2.alias = node.alias
193
+ return node2
217
194
 
195
+ fnc_name = node.op.lower()
218
196
  if fnc_name == "database" and len(node.args) == 0:
219
197
  if session is not None:
220
198
  cur_db = session.database
@@ -1,5 +1,4 @@
1
1
  import os
2
- import secrets
3
2
  import mimetypes
4
3
  import threading
5
4
  import traceback
@@ -27,7 +26,7 @@ from mindsdb.api.http.namespaces.chatbots import ns_conf as chatbots_ns
27
26
  from mindsdb.api.http.namespaces.jobs import ns_conf as jobs_ns
28
27
  from mindsdb.api.http.namespaces.config import ns_conf as conf_ns
29
28
  from mindsdb.api.http.namespaces.databases import ns_conf as databases_ns
30
- from mindsdb.api.http.namespaces.default import ns_conf as default_ns, check_auth
29
+ from mindsdb.api.http.namespaces.default import ns_conf as default_ns
31
30
  from mindsdb.api.http.namespaces.file import ns_conf as file_ns
32
31
  from mindsdb.api.http.namespaces.handlers import ns_conf as handlers_ns
33
32
  from mindsdb.api.http.namespaces.knowledge_bases import ns_conf as knowledge_bases_ns
@@ -47,12 +46,13 @@ from mindsdb.interfaces.jobs.jobs_controller import JobsController
47
46
  from mindsdb.interfaces.storage import db
48
47
  from mindsdb.metrics.server import init_metrics
49
48
  from mindsdb.utilities import log
50
- from mindsdb.utilities.config import Config
49
+ from mindsdb.utilities.config import config
51
50
  from mindsdb.utilities.context import context as ctx
52
51
  from mindsdb.utilities.json_encoder import CustomJSONProvider
53
52
  from mindsdb.utilities.ps import is_pid_listen_port, wait_func_is_true
54
53
  from mindsdb.utilities.sentry import sentry_sdk # noqa: F401
55
54
  from mindsdb.utilities.otel import trace # noqa: F401
55
+ from mindsdb.api.common.middleware import verify_pat
56
56
 
57
57
  logger = log.getLogger(__name__)
58
58
 
@@ -95,7 +95,7 @@ def custom_output_json(data, code, headers=None):
95
95
  return resp
96
96
 
97
97
 
98
- def get_last_compatible_gui_version() -> Version:
98
+ def get_last_compatible_gui_version() -> Version | bool:
99
99
  logger.debug("Getting last compatible frontend..")
100
100
  try:
101
101
  res = requests.get(
@@ -164,7 +164,6 @@ def get_last_compatible_gui_version() -> Version:
164
164
 
165
165
  def get_current_gui_version() -> Version:
166
166
  logger.debug("Getting current frontend version...")
167
- config = Config()
168
167
  static_path = Path(config["paths"]["static"])
169
168
  version_txt_path = static_path.joinpath("version.txt")
170
169
 
@@ -181,7 +180,6 @@ def get_current_gui_version() -> Version:
181
180
 
182
181
  def initialize_static():
183
182
  logger.debug("Initializing static..")
184
- config = Config()
185
183
  last_gui_version_lv = get_last_compatible_gui_version()
186
184
  current_gui_version_lv = get_current_gui_version()
187
185
  required_gui_version = config["gui"].get("version")
@@ -190,17 +188,18 @@ def initialize_static():
190
188
  required_gui_version_lv = parse_version(required_gui_version)
191
189
  success = True
192
190
  if current_gui_version_lv is None or required_gui_version_lv != current_gui_version_lv:
193
- logger.debug("Updating gui..")
194
191
  success = update_static(required_gui_version_lv)
195
192
  else:
196
193
  if last_gui_version_lv is False:
194
+ logger.debug(
195
+ "The number of the latest version has not been determined, "
196
+ f"so we will continue using the current version: {current_gui_version_lv}"
197
+ )
197
198
  return False
198
199
 
199
- # ignore versions like '23.9.2.2'
200
- if current_gui_version_lv is not None and len(current_gui_version_lv.release) < 3:
201
- if current_gui_version_lv == last_gui_version_lv:
202
- return True
203
- logger.debug("Updating gui..")
200
+ if current_gui_version_lv == last_gui_version_lv:
201
+ logger.debug(f"The latest version is already in use: {current_gui_version_lv}")
202
+ return True
204
203
  success = update_static(last_gui_version_lv)
205
204
 
206
205
  if db.session:
@@ -208,21 +207,22 @@ def initialize_static():
208
207
  return success
209
208
 
210
209
 
211
- def initialize_app(config, no_studio):
210
+ def initialize_app():
212
211
  static_root = config["paths"]["static"]
213
212
  logger.debug(f"Static route: {static_root}")
214
213
  gui_exists = Path(static_root).joinpath("index.html").is_file()
215
214
  logger.debug(f"Does GUI already exist.. {'YES' if gui_exists else 'NO'}")
216
215
  init_static_thread = None
217
- if no_studio is False and (config["gui"]["autoupdate"] is True or gui_exists is False):
216
+
217
+ if config["gui"]["autoupdate"] is True or (config["gui"]["open_on_start"] is True and gui_exists is False):
218
218
  init_static_thread = threading.Thread(target=initialize_static, name="initialize_static")
219
219
  init_static_thread.start()
220
220
 
221
221
  # Wait for static initialization.
222
- if not no_studio and init_static_thread is not None:
222
+ if config["gui"]["open_on_start"] is True and init_static_thread is not None:
223
223
  init_static_thread.join()
224
224
 
225
- app, api = initialize_flask(config, init_static_thread, no_studio)
225
+ app, api = initialize_flask(config, init_static_thread)
226
226
  Compress(app)
227
227
 
228
228
  initialize_interfaces(app)
@@ -312,14 +312,20 @@ def initialize_app(config, no_studio):
312
312
  def before_request():
313
313
  logger.debug(f"HTTP {request.method}: {request.path}")
314
314
  ctx.set_default()
315
- config = Config()
315
+
316
+ h = request.headers.get("Authorization")
317
+ if not h or not h.startswith("Bearer "):
318
+ bearer = None
319
+ else:
320
+ bearer = h.split(" ", 1)[1].strip() or None
316
321
 
317
322
  # region routes where auth is required
318
323
  if (
319
324
  config["auth"]["http_auth_enabled"] is True
320
325
  and any(request.path.startswith(f"/api{ns.path}") for ns in protected_namespaces)
321
- and check_auth() is False
326
+ and verify_pat(bearer) is False
322
327
  ):
328
+ logger.debug(f"Auth failed for path {request.path}")
323
329
  return http_error(
324
330
  HTTPStatus.UNAUTHORIZED,
325
331
  "Unauthorized",
@@ -340,29 +346,23 @@ def initialize_app(config, no_studio):
340
346
  except Exception:
341
347
  user_id = 0
342
348
 
343
- try:
344
- session_id = request.cookies.get("session")
345
- except Exception:
346
- session_id = "unknown"
347
-
348
349
  if company_id is not None:
349
350
  try:
350
351
  company_id = int(company_id)
351
352
  except Exception as e:
352
- logger.error(f"Cloud not parse company id: {company_id} | exception: {e}")
353
+ logger.error(f"Could not parse company id: {company_id} | exception: {e}")
353
354
  company_id = None
354
355
 
355
356
  if user_class is not None:
356
357
  try:
357
358
  user_class = int(user_class)
358
359
  except Exception as e:
359
- logger.error(f"Cloud not parse user_class: {user_class} | exception: {e}")
360
+ logger.error(f"Could not parse user_class: {user_class} | exception: {e}")
360
361
  user_class = 0
361
362
  else:
362
363
  user_class = 0
363
364
 
364
365
  ctx.user_id = user_id
365
- ctx.session_id = session_id
366
366
  ctx.company_id = company_id
367
367
  ctx.user_class = user_class
368
368
  ctx.email_confirmed = email_confirmed
@@ -371,21 +371,18 @@ def initialize_app(config, no_studio):
371
371
  return app
372
372
 
373
373
 
374
- def initialize_flask(config, init_static_thread, no_studio):
374
+ def initialize_flask(config, init_static_thread):
375
375
  logger.debug("Initializing flask..")
376
376
  # region required for windows https://github.com/mindsdb/mindsdb/issues/2526
377
377
  mimetypes.add_type("text/css", ".css")
378
378
  mimetypes.add_type("text/javascript", ".js")
379
379
  # endregion
380
380
 
381
- kwargs = {}
382
- if no_studio is not True:
383
- static_path = os.path.join(config["paths"]["static"], "static/")
384
- if os.path.isabs(static_path) is False:
385
- static_path = os.path.join(os.getcwd(), static_path)
386
- kwargs["static_url_path"] = "/static"
387
- kwargs["static_folder"] = static_path
388
- logger.debug(f"Static path: {static_path}")
381
+ static_path = os.path.join(config["paths"]["static"], "static/")
382
+ if os.path.isabs(static_path) is False:
383
+ static_path = os.path.join(os.getcwd(), static_path)
384
+ kwargs = {"static_url_path": "/static", "static_folder": static_path}
385
+ logger.debug(f"Static path: {static_path}")
389
386
 
390
387
  app = Flask(__name__, **kwargs)
391
388
  init_metrics(app)
@@ -394,14 +391,11 @@ def initialize_flask(config, init_static_thread, no_studio):
394
391
  FlaskInstrumentor().instrument_app(app)
395
392
  RequestsInstrumentor().instrument()
396
393
 
397
- app.config["SECRET_KEY"] = os.environ.get("FLASK_SECRET_KEY", secrets.token_hex(32))
398
- app.config["SESSION_COOKIE_NAME"] = "session"
399
- app.config["PERMANENT_SESSION_LIFETIME"] = config["auth"]["http_permanent_session_lifetime"]
400
394
  app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 60
401
395
  app.config["SWAGGER_HOST"] = "http://localhost:8000/mindsdb"
402
396
  app.json = CustomJSONProvider()
403
397
 
404
- authorizations = {"apikey": {"type": "session", "in": "query", "name": "session"}}
398
+ authorizations = {"apikey": {"type": "apiKey", "in": "header", "name": "Authorization"}}
405
399
 
406
400
  logger.debug("Creating swagger API..")
407
401
  api = Swagger_Api(
@@ -418,8 +412,7 @@ def initialize_flask(config, init_static_thread, no_studio):
418
412
  port = config["api"]["http"]["port"]
419
413
  host = config["api"]["http"]["host"]
420
414
 
421
- # NOTE rewrite it, that hotfix to see GUI link
422
- if not no_studio:
415
+ if config["gui"]["open_on_start"]:
423
416
  if host in ("", "0.0.0.0"):
424
417
  url = f"http://127.0.0.1:{port}/"
425
418
  else:
@@ -443,8 +436,6 @@ def initialize_interfaces(app):
443
436
  app.database_controller = DatabaseController()
444
437
  app.file_controller = FileController()
445
438
  app.jobs_controller = JobsController()
446
- config = Config()
447
- app.config_obj = config
448
439
 
449
440
 
450
441
  def _open_webbrowser(url: str, pid: int, port: int, init_static_thread, static_folder):
@@ -4,7 +4,7 @@ import time
4
4
  import urllib
5
5
 
6
6
  import requests
7
- from flask import redirect, request, session, url_for
7
+ from flask import redirect, request, url_for
8
8
  from flask_restx import Resource
9
9
 
10
10
  from mindsdb.api.http.namespaces.configs.auth import ns_conf
@@ -21,9 +21,7 @@ def get_access_token() -> str:
21
21
  Returns:
22
22
  str: token
23
23
  """
24
- return (
25
- Config().get("auth", {}).get("oauth", {}).get("tokens", {}).get("access_token")
26
- )
24
+ return Config().get("auth", {}).get("oauth", {}).get("tokens", {}).get("access_token")
27
25
 
28
26
 
29
27
  def request_user_info(access_token: str = None) -> dict:
@@ -58,7 +56,7 @@ def request_user_info(access_token: str = None) -> dict:
58
56
  @ns_conf.hide
59
57
  class Auth(Resource):
60
58
  @ns_conf.doc(params={"code": "authentification code"})
61
- @api_endpoint_metrics('GET', '/auth/code')
59
+ @api_endpoint_metrics("GET", "/auth/code")
62
60
  def get(self):
63
61
  """callback from auth server if authentification is successful"""
64
62
  config = Config()
@@ -72,9 +70,7 @@ class Auth(Resource):
72
70
  client_id = oauth_meta["client_id"]
73
71
  client_secret = oauth_meta["client_secret"]
74
72
  auth_server = oauth_meta["server_host"]
75
- client_basic = base64.b64encode(
76
- f"{client_id}:{client_secret}".encode()
77
- ).decode()
73
+ client_basic = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
78
74
 
79
75
  redirect_uri = f"https://{public_hostname}{request.path}"
80
76
  response = requests.post(
@@ -115,7 +111,7 @@ class Auth(Resource):
115
111
  "public_hostname": public_hostname,
116
112
  "ami_id": aws_meta_data.get("ami-id"),
117
113
  },
118
- headers={"Authorization": f'Bearer {tokens["access_token"]}'},
114
+ headers={"Authorization": f"Bearer {tokens['access_token']}"},
119
115
  timeout=5,
120
116
  )
121
117
  if resp.status_code != 200:
@@ -123,10 +119,6 @@ class Auth(Resource):
123
119
  except Exception as e:
124
120
  logger.warning(f"Cant't send request to cloud server: {e}")
125
121
 
126
- session["username"] = user_data["name"]
127
- session["auth_provider"] = "cloud"
128
- session.permanent = True
129
-
130
122
  if request.path.endswith("/auth/callback/cloud_home"):
131
123
  return redirect(f"https://{auth_server}")
132
124
  else:
@@ -140,7 +132,7 @@ class CloudLoginRoute(Resource):
140
132
  responses={302: "Redirect to auth server"},
141
133
  params={"location": "final redirection should lead to that location"},
142
134
  )
143
- @api_endpoint_metrics('GET', '/auth/cloud_login')
135
+ @api_endpoint_metrics("GET", "/auth/cloud_login")
144
136
  def get(self):
145
137
  """redirect to cloud login form"""
146
138
  location = request.args.get("location")
@@ -32,8 +32,6 @@ class GetConfig(Resource):
32
32
  value = config.get(key)
33
33
  if value is not None:
34
34
  resp[key] = value
35
- if "a2a" in config["api"]:
36
- resp["a2a"] = config["api"]["a2a"]
37
35
  return resp
38
36
 
39
37
  @ns_conf.doc("put_config")
@@ -1,6 +1,4 @@
1
- import time
2
-
3
- from flask import request, session
1
+ from flask import request
4
2
  from flask_restx import Resource
5
3
  from flask_restx import fields
6
4
 
@@ -10,143 +8,113 @@ from mindsdb.api.http.utils import http_error
10
8
  from mindsdb.metrics.metrics import api_endpoint_metrics
11
9
  from mindsdb.utilities.config import Config
12
10
  from mindsdb.utilities import log
11
+ from mindsdb.api.common.middleware import generate_pat, revoke_pat, verify_pat
13
12
 
14
13
 
15
14
  logger = log.getLogger(__name__)
16
15
 
17
16
 
18
- def check_auth() -> bool:
19
- ''' checking whether current user is authenticated
20
-
21
- Returns:
22
- bool: True if user authentication is approved
23
- '''
24
- config = Config()
25
- if config['auth']['http_auth_enabled'] is False:
26
- return True
27
-
28
- if config['auth'].get('provider') == 'cloud':
29
- if isinstance(session.get('username'), str) is False:
30
- return False
31
-
32
- if config['auth']['oauth']['tokens']['expires_at'] < time.time():
33
- return False
34
-
35
- return True
36
-
37
- return session.get('username') == config['auth']['username']
38
-
39
-
40
- @ns_conf.route('/login', methods=['POST'])
17
+ @ns_conf.route("/login", methods=["POST"])
41
18
  class LoginRoute(Resource):
42
19
  @ns_conf.doc(
43
- responses={
44
- 200: 'Success',
45
- 400: 'Error in username or password',
46
- 401: 'Invalid username or password'
47
- },
48
- body=ns_conf.model('request_login', {
49
- 'username': fields.String(description='Username'),
50
- 'password': fields.String(description='Password')
51
- })
20
+ responses={200: "Success", 400: "Error in username or password", 401: "Invalid username or password"},
21
+ body=ns_conf.model(
22
+ "request_login",
23
+ {"username": fields.String(description="Username"), "password": fields.String(description="Password")},
24
+ ),
52
25
  )
53
- @api_endpoint_metrics('POST', '/default/login')
26
+ @api_endpoint_metrics("POST", "/default/login")
54
27
  def post(self):
55
- ''' Check user's credentials and creates a session
56
- '''
57
- username = request.json.get('username')
58
- password = request.json.get('password')
28
+ """Check user's credentials and creates a session"""
29
+ username = request.json.get("username")
30
+ password = request.json.get("password")
59
31
  if (
60
- isinstance(username, str) is False or len(username) == 0
61
- or isinstance(password, str) is False or len(password) == 0
32
+ isinstance(username, str) is False
33
+ or len(username) == 0
34
+ or isinstance(password, str) is False
35
+ or len(password) == 0
62
36
  ):
63
- return http_error(
64
- 400, 'Error in username or password',
65
- 'Username and password should be string'
66
- )
37
+ return http_error(400, "Error in username or password", "Username and password should be string")
67
38
 
68
39
  config = Config()
69
- inline_username = config['auth']['username']
70
- inline_password = config['auth']['password']
40
+ inline_username = config["auth"]["username"]
41
+ inline_password = config["auth"]["password"]
71
42
 
72
- if (
73
- username != inline_username
74
- or password != inline_password
75
- ):
76
- return http_error(
77
- 401, 'Forbidden',
78
- 'Invalid username or password'
79
- )
43
+ if username != inline_username or password != inline_password:
44
+ return http_error(401, "Forbidden", "Invalid username or password")
80
45
 
81
- session.clear()
82
- session['username'] = username
83
- session.permanent = True
46
+ logger.info(f"User '{username}' logged in successfully")
84
47
 
85
- return '', 200
48
+ return {"token": generate_pat()}, 200
86
49
 
87
50
 
88
- @ns_conf.route('/logout', methods=['POST'])
51
+ @ns_conf.route("/logout", methods=["POST"])
89
52
  class LogoutRoute(Resource):
90
- @ns_conf.doc(
91
- responses={
92
- 200: 'Success'
93
- }
94
- )
95
- @api_endpoint_metrics('POST', '/default/logout')
53
+ @ns_conf.doc(responses={200: "Success"})
54
+ @api_endpoint_metrics("POST", "/default/logout")
96
55
  def post(self):
97
- session.clear()
98
- return '', 200
56
+ # We can't forcibly log out a user with the
57
+ h = request.headers.get("Authorization")
58
+ if not h or not h.startswith("Bearer "):
59
+ bearer = None
60
+ else:
61
+ bearer = h.split(" ", 1)[1].strip() or None
62
+ revoke_pat(bearer)
63
+ return "", 200
99
64
 
100
65
 
101
- @ns_conf.route('/status')
66
+ @ns_conf.route("/status")
102
67
  class StatusRoute(Resource):
103
68
  @ns_conf.doc(
104
- responses={
105
- 200: 'Success'
106
- },
107
- model=ns_conf.model('response_status', {
108
- 'environment': fields.String(description='The name of current environment: cloud, local or other'),
109
- 'mindsdb_version': fields.String(description='Current version of mindsdb'),
110
- 'auth': fields.Nested(
111
- ns_conf.model('response_status_auth', {
112
- 'confirmed': fields.Boolean(description='is current user authenticated'),
113
- 'required': fields.Boolean(description='is authenticated required'),
114
- 'provider': fields.Boolean(description='current authenticated provider: local of 3d-party')
115
- })
116
- )
117
- })
69
+ responses={200: "Success"},
70
+ model=ns_conf.model(
71
+ "response_status",
72
+ {
73
+ "environment": fields.String(description="The name of current environment: cloud, local or other"),
74
+ "mindsdb_version": fields.String(description="Current version of mindsdb"),
75
+ "auth": fields.Nested(
76
+ ns_conf.model(
77
+ "response_status_auth",
78
+ {
79
+ "confirmed": fields.Boolean(description="is current user authenticated"),
80
+ "required": fields.Boolean(description="is authenticated required"),
81
+ "provider": fields.Boolean(description="current authenticated provider: local of 3d-party"),
82
+ },
83
+ )
84
+ ),
85
+ },
86
+ ),
118
87
  )
119
- @api_endpoint_metrics('GET', '/default/status')
88
+ @api_endpoint_metrics("GET", "/default/status")
120
89
  def get(self):
121
- ''' returns auth and environment data
122
- '''
123
- environment = 'local'
90
+ """returns auth and environment data"""
91
+ environment = "local"
124
92
  config = Config()
125
93
 
126
- environment = config.get('environment')
94
+ environment = config.get("environment")
127
95
  if environment is None:
128
- if config.get('cloud', False):
129
- environment = 'cloud'
130
- elif config.get('aws_marketplace', False):
131
- environment = 'aws_marketplace'
96
+ if config.get("cloud", False):
97
+ environment = "cloud"
98
+ elif config.get("aws_marketplace", False):
99
+ environment = "aws_marketplace"
132
100
  else:
133
- environment = 'local'
101
+ environment = "local"
134
102
 
135
- auth_provider = 'disabled'
136
- if config['auth']['http_auth_enabled'] is True:
137
- if config['auth'].get('provider') is not None:
138
- auth_provider = config['auth'].get('provider')
103
+ auth_provider = "disabled"
104
+ if config["auth"]["http_auth_enabled"] is True:
105
+ if config["auth"].get("provider") is not None:
106
+ auth_provider = config["auth"].get("provider")
139
107
  else:
140
- auth_provider = 'local'
108
+ auth_provider = "local"
141
109
 
142
110
  resp = {
143
- 'mindsdb_version': mindsdb_version,
144
- 'environment': environment,
145
- 'auth': {
146
- 'confirmed': check_auth(),
147
- 'http_auth_enabled': config['auth']['http_auth_enabled'],
148
- 'provider': auth_provider
149
- }
111
+ "mindsdb_version": mindsdb_version,
112
+ "environment": environment,
113
+ "auth": {
114
+ "confirmed": verify_pat(request.headers.get("Authorization", "").replace("Bearer ", "")),
115
+ "http_auth_enabled": config["auth"]["http_auth_enabled"],
116
+ "provider": auth_provider,
117
+ },
150
118
  }
151
119
 
152
120
  return resp
@@ -3,6 +3,7 @@ import shutil
3
3
  import tarfile
4
4
  import tempfile
5
5
  import zipfile
6
+ from pathlib import Path
6
7
  from urllib.parse import urlparse
7
8
 
8
9
  import multipart
@@ -60,7 +61,10 @@ class File(Resource):
60
61
 
61
62
  def on_file(file):
62
63
  nonlocal file_object
63
- data["file"] = file.file_name.decode()
64
+ file_name = file.file_name.decode()
65
+ data["file"] = file_name
66
+ if Path(file_name).name != file_name:
67
+ raise ValueError(f"Wrong file name: {file_name}")
64
68
  file_object = file.file_object
65
69
 
66
70
  temp_dir_path = tempfile.mkdtemp(prefix="mindsdb_file_")
@@ -72,8 +76,9 @@ class File(Resource):
72
76
  on_file=on_file,
73
77
  config={
74
78
  "UPLOAD_DIR": temp_dir_path.encode(), # bytes required
75
- "UPLOAD_KEEP_FILENAME": True,
79
+ "UPLOAD_KEEP_FILENAME": False,
76
80
  "UPLOAD_KEEP_EXTENSIONS": True,
81
+ "UPLOAD_DELETE_TMP": False,
77
82
  "MAX_MEMORY_FILE_SIZE": 0,
78
83
  },
79
84
  )
@@ -93,6 +98,7 @@ class File(Resource):
93
98
  except (AttributeError, ValueError, OSError):
94
99
  logger.debug("Failed to flush file_object before closing.", exc_info=True)
95
100
  file_object.close()
101
+ Path(file_object.name).rename(Path(file_object.name).parent / data["file"])
96
102
  file_object = None
97
103
  else:
98
104
  data = request.json
@@ -101,7 +107,7 @@ class File(Resource):
101
107
  return http_error(
102
108
  400,
103
109
  "File already exists",
104
- f"File with name '{data['file']}' already exists",
110
+ f"File with name '{mindsdb_file_name}' already exists",
105
111
  )
106
112
 
107
113
  if data.get("source_type") == "url":