# Copyright (C) 2011-2024 RhodeCode GmbH # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License, version 3 # (only), as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # This program is dual-licensed. If you wish to learn more about the # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ import logging import itertools from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods from rhodecode.api.utils import Optional, OAttr, has_superadmin_permission, get_user_or_error from rhodecode.lib.utils import get_rhodecode_repo_store_path from rhodecode.lib import system_info from rhodecode.lib import user_sessions from rhodecode.lib import exc_tracking from rhodecode.lib.ext_json import json from rhodecode.lib.utils2 import safe_int from rhodecode.model.db import UserIpMap from rhodecode.model.scm import ScmModel log = logging.getLogger(__name__) @jsonrpc_method() def get_server_info(request, apiuser): """ Returns the |RCE| server information. This includes the running version of |RCE| and all installed packages. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser Example output: .. code-block:: bash id : result : { 'modules': [,...] 'py_version': , 'platform': , 'rhodecode_version': } error : null """ if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() server_info = ScmModel().get_server_info(request.environ) # rhodecode-index requires those server_info["index_storage"] = server_info["search"]["value"]["location"] server_info["storage"] = server_info["storage"]["value"]["path"] return server_info @jsonrpc_method() def get_repo_store(request, apiuser): """ Returns the |RCE| repository storage information. :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser Example output: .. code-block:: bash id : result : { 'modules': [,...] 'py_version': , 'platform': , 'rhodecode_version': } error : null """ if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() path = get_rhodecode_repo_store_path() return {"path": path} @jsonrpc_method() def get_ip(request, apiuser, userid=Optional(OAttr("apiuser"))): """ Displays the IP Address as seen from the |RCE| server. * This command displays the IP Address, as well as all the defined IP addresses for the specified user. If the ``userid`` is not set, the data returned is for the user calling the method. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from |authtoken|. :type apiuser: AuthUser :param userid: Sets the userid for which associated IP Address data is returned. :type userid: Optional(str or int) Example output: .. code-block:: bash id : result : { "server_ip_addr": "", "user_ips": [ { "ip_addr": "", "ip_range": ["", ""], }, ... ] } """ if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() userid = Optional.extract(userid, evaluate_locals=locals()) userid = getattr(userid, "user_id", userid) user = get_user_or_error(userid) ips = UserIpMap.query().filter(UserIpMap.user == user).all() return {"server_ip_addr": request.rpc_ip_addr, "user_ips": ips} @jsonrpc_method() def rescan_repos(request, apiuser): """ Triggers a rescan of the specified repositories. It returns list of added repositories, and errors during scan. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser Example output: .. code-block:: bash id : result : { 'added': [,...] 'errors': [,...] } error : null Example error output: .. code-block:: bash id : result : null error : { 'Error occurred during rescan repositories action' } """ from rhodecode.lib.utils import repo2db_mapper # re-import for testing patches if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() try: added, errors = repo2db_mapper(ScmModel().repo_scan(), force_hooks_rebuild=True) return {"added": added, "errors": errors} except Exception: log.exception("Failed to run repo rescan") raise JSONRPCError("Error occurred during rescan repositories action") @jsonrpc_method() def cleanup_repos(request, apiuser): """ Triggers a cleanup of non-existing repositories or repository groups in filesystem. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser Example output: .. code-block:: bash id : result : { 'removed': [,...] 'errors': [,...] } error : null Example error output: .. code-block:: bash id : result : null error : { 'Error occurred during repo storage cleanup action' } """ from rhodecode.lib.utils import repo2db_cleanup # re-import for testing patches if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() try: removed, errors = repo2db_cleanup() return {"removed": removed, "errors": errors} except Exception: log.exception("Failed to run repo storage cleanup") raise JSONRPCError("Error occurred during repo storage cleanup action") @jsonrpc_method() def cleanup_sessions(request, apiuser, older_then=Optional(60)): """ Triggers a session cleanup action. If the ``older_then`` option is set, only sessions that hasn't been accessed in the given number of days will be removed. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param older_then: Deletes session that hasn't been accessed in given number of days. :type older_then: Optional(int) Example output: .. code-block:: bash id : result: { "backend": "", "sessions_removed": } error : null Example error output: .. code-block:: bash id : result : null error : { 'Error occurred during session cleanup' } """ if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() older_then = safe_int(Optional.extract(older_then)) or 60 older_than_seconds = 60 * 60 * 24 * older_then config = system_info.rhodecode_config().get_value()["value"]["config"] session_model = user_sessions.get_session_handler("cookie")(config) backend = session_model.SESSION_TYPE try: cleaned = session_model.clean_sessions(older_than_seconds=older_than_seconds) return {"sessions_removed": cleaned, "backend": backend} except user_sessions.CleanupCommand as msg: return {"cleanup_command": str(msg), "backend": backend} except Exception as e: log.exception("Failed session cleanup") raise JSONRPCError("Error occurred during session cleanup") @jsonrpc_method() def get_method(request, apiuser, pattern=Optional("*")): """ Returns list of all available API methods. By default match pattern os "*" but any other pattern can be specified. eg *comment* will return all methods with comment inside them. If just single method is matched returned data will also include method specification This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param pattern: pattern to match method names against :type pattern: Optional("*") Example output: .. code-block:: bash id : "result": [ "changeset_comment", "comment_pull_request", "comment_commit" ] error : null .. code-block:: bash id : "result": [ "comment_commit", { "apiuser": "", "comment_type": "", "commit_id": "", "message": "", "repoid": "", "request": "", "resolves_comment_id": "", "status": "", "userid": ">" } ] error : null """ from rhodecode.config import patches inspect = patches.inspect_getargspec() if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() pattern = Optional.extract(pattern) matches = find_methods(request.registry.jsonrpc_methods, pattern) args_desc = [] matches_keys = list(matches.keys()) if len(matches_keys) == 1: func = matches[matches_keys[0]] argspec = inspect.getargspec(func) arglist = argspec[0] defaults = list(map(repr, argspec[3] or [])) default_empty = "" # kw arguments required by this method func_kwargs = dict(itertools.zip_longest(reversed(arglist), reversed(defaults), fillvalue=default_empty)) args_desc.append(func_kwargs) return matches_keys + args_desc @jsonrpc_method() def store_exception(request, apiuser, exc_data_json, prefix=Optional("rhodecode")): """ Stores sent exception inside the built-in exception tracker in |RCE| server. This command can only be run using an |authtoken| with admin rights to the specified repository. This command takes the following options: :param apiuser: This is filled automatically from the |authtoken|. :type apiuser: AuthUser :param exc_data_json: JSON data with exception e.g {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"} :type exc_data_json: JSON data :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools' :type prefix: Optional("rhodecode") Example output: .. code-block:: bash id : "result": { "exc_id": 139718459226384, "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384" } error : null """ if not has_superadmin_permission(apiuser): raise JSONRPCForbidden() prefix = Optional.extract(prefix) exc_id = exc_tracking.generate_id() try: exc_data = json.loads(exc_data_json) except Exception: log.error("Failed to parse JSON: %r", exc_data_json) raise JSONRPCError( "Failed to parse JSON data from exc_data_json field. Please make sure it contains a valid JSON." ) try: exc_traceback = exc_data["exc_traceback"] exc_type_name = exc_data["exc_type_name"] exc_value = "" except KeyError as err: raise JSONRPCError(f"Missing exc_traceback, or exc_type_name in exc_data_json field. Missing: {err}") class ExcType: __name__ = exc_type_name exc_info = (ExcType(), exc_value, exc_traceback) exc_tracking._store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix) exc_url = request.route_url("admin_settings_exception_tracker_show", exception_id=exc_id) return {"exc_id": exc_id, "exc_url": exc_url}