# 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(
config.get('beaker.session.type', 'memory'))(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 '
f'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}