437 lines
16 KiB
Python
437 lines
16 KiB
Python
# Copyright (C) 2016-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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
# 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 re
|
|
import logging
|
|
import formencode
|
|
import formencode.htmlfill
|
|
import datetime
|
|
from pyramid.interfaces import IRoutesMapper
|
|
|
|
from pyramid.httpexceptions import HTTPFound
|
|
from pyramid.renderers import render
|
|
from pyramid.response import Response
|
|
|
|
from rhodecode.apps._base import BaseAppView, DataGridAppView
|
|
from rhodecode.apps.ssh_support.events import SshKeyFileChangeEvent
|
|
from rhodecode import events
|
|
|
|
from rhodecode.lib import helpers as h
|
|
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, CSRFRequired
|
|
from rhodecode.lib.utils2 import aslist, safe_str
|
|
from rhodecode.model.db import or_, coalesce, User, UserIpMap, UserSshKeys
|
|
from rhodecode.model.forms import ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm
|
|
from rhodecode.model.meta import Session
|
|
from rhodecode.model.permission import PermissionModel
|
|
from rhodecode.model.settings import SettingsModel
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class AdminPermissionsView(BaseAppView, DataGridAppView):
|
|
def load_default_context(self):
|
|
c = self._get_local_tmpl_context()
|
|
PermissionModel().set_global_permission_choices(c, gettext_translator=self.request.translate)
|
|
return c
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def permissions_application(self):
|
|
c = self.load_default_context()
|
|
c.active = "application"
|
|
|
|
c.user = User.get_default_user(refresh=True)
|
|
|
|
app_settings = c.rc_config
|
|
|
|
defaults = {
|
|
"anonymous": c.user.active,
|
|
"default_register_message": app_settings.get("rhodecode_register_message"),
|
|
}
|
|
defaults.update(c.user.get_default_perms())
|
|
|
|
data = render(
|
|
"rhodecode:templates/admin/permissions/permissions.mako", self._get_template_context(c), self.request
|
|
)
|
|
html = formencode.htmlfill.render(data, defaults=defaults, encoding="UTF-8", force_defaults=False)
|
|
return Response(html)
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
@CSRFRequired()
|
|
def permissions_application_update(self):
|
|
_ = self.request.translate
|
|
c = self.load_default_context()
|
|
c.active = "application"
|
|
|
|
_form = ApplicationPermissionsForm(
|
|
self.request.translate,
|
|
[x[0] for x in c.register_choices],
|
|
[x[0] for x in c.password_reset_choices],
|
|
[x[0] for x in c.extern_activate_choices],
|
|
)()
|
|
|
|
try:
|
|
form_result = _form.to_python(dict(self.request.POST))
|
|
form_result.update({"perm_user_name": User.DEFAULT_USER})
|
|
PermissionModel().update_application_permissions(form_result)
|
|
|
|
settings = [
|
|
("register_message", "default_register_message"),
|
|
]
|
|
for setting, form_key in settings:
|
|
sett = SettingsModel().create_or_update_setting(setting, form_result[form_key])
|
|
Session().add(sett)
|
|
|
|
Session().commit()
|
|
h.flash(_("Application permissions updated successfully"), category="success")
|
|
|
|
except formencode.Invalid as errors:
|
|
defaults = errors.value
|
|
|
|
data = render(
|
|
"rhodecode:templates/admin/permissions/permissions.mako", self._get_template_context(c), self.request
|
|
)
|
|
html = formencode.htmlfill.render(
|
|
data,
|
|
defaults=defaults,
|
|
errors=errors.unpack_errors() or {},
|
|
prefix_error=False,
|
|
encoding="UTF-8",
|
|
force_defaults=False,
|
|
)
|
|
return Response(html)
|
|
|
|
except Exception:
|
|
log.exception("Exception during update of permissions")
|
|
h.flash(_("Error occurred during update of permissions"), category="error")
|
|
|
|
affected_user_ids = [User.get_default_user_id()]
|
|
PermissionModel().trigger_permission_flush(affected_user_ids)
|
|
|
|
raise HTTPFound(h.route_path("admin_permissions_application"))
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def permissions_objects(self):
|
|
c = self.load_default_context()
|
|
c.active = "objects"
|
|
|
|
c.user = User.get_default_user(refresh=True)
|
|
defaults = {}
|
|
defaults.update(c.user.get_default_perms())
|
|
|
|
data = render(
|
|
"rhodecode:templates/admin/permissions/permissions.mako", self._get_template_context(c), self.request
|
|
)
|
|
html = formencode.htmlfill.render(data, defaults=defaults, encoding="UTF-8", force_defaults=False)
|
|
return Response(html)
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
@CSRFRequired()
|
|
def permissions_objects_update(self):
|
|
_ = self.request.translate
|
|
c = self.load_default_context()
|
|
c.active = "objects"
|
|
|
|
_form = ObjectPermissionsForm(
|
|
self.request.translate,
|
|
[x[0] for x in c.repo_perms_choices],
|
|
[x[0] for x in c.group_perms_choices],
|
|
[x[0] for x in c.user_group_perms_choices],
|
|
)()
|
|
|
|
try:
|
|
form_result = _form.to_python(dict(self.request.POST))
|
|
form_result.update({"perm_user_name": User.DEFAULT_USER})
|
|
PermissionModel().update_object_permissions(form_result)
|
|
|
|
Session().commit()
|
|
h.flash(_("Object permissions updated successfully"), category="success")
|
|
|
|
except formencode.Invalid as errors:
|
|
defaults = errors.value
|
|
|
|
data = render(
|
|
"rhodecode:templates/admin/permissions/permissions.mako", self._get_template_context(c), self.request
|
|
)
|
|
html = formencode.htmlfill.render(
|
|
data,
|
|
defaults=defaults,
|
|
errors=errors.unpack_errors() or {},
|
|
prefix_error=False,
|
|
encoding="UTF-8",
|
|
force_defaults=False,
|
|
)
|
|
return Response(html)
|
|
except Exception:
|
|
log.exception("Exception during update of permissions")
|
|
h.flash(_("Error occurred during update of permissions"), category="error")
|
|
|
|
affected_user_ids = [User.get_default_user_id()]
|
|
PermissionModel().trigger_permission_flush(affected_user_ids)
|
|
|
|
raise HTTPFound(h.route_path("admin_permissions_object"))
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def permissions_branch(self):
|
|
c = self.load_default_context()
|
|
c.active = "branch"
|
|
|
|
c.user = User.get_default_user(refresh=True)
|
|
defaults = {}
|
|
defaults.update(c.user.get_default_perms())
|
|
|
|
data = render(
|
|
"rhodecode:templates/admin/permissions/permissions.mako", self._get_template_context(c), self.request
|
|
)
|
|
html = formencode.htmlfill.render(data, defaults=defaults, encoding="UTF-8", force_defaults=False)
|
|
return Response(html)
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def permissions_global(self):
|
|
c = self.load_default_context()
|
|
c.active = "global"
|
|
|
|
c.user = User.get_default_user(refresh=True)
|
|
defaults = {}
|
|
defaults.update(c.user.get_default_perms())
|
|
|
|
data = render(
|
|
"rhodecode:templates/admin/permissions/permissions.mako", self._get_template_context(c), self.request
|
|
)
|
|
html = formencode.htmlfill.render(data, defaults=defaults, encoding="UTF-8", force_defaults=False)
|
|
return Response(html)
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
@CSRFRequired()
|
|
def permissions_global_update(self):
|
|
_ = self.request.translate
|
|
c = self.load_default_context()
|
|
c.active = "global"
|
|
|
|
_form = UserPermissionsForm(
|
|
self.request.translate,
|
|
[x[0] for x in c.repo_create_choices],
|
|
[x[0] for x in c.repo_create_on_write_choices],
|
|
[x[0] for x in c.repo_group_create_choices],
|
|
[x[0] for x in c.user_group_create_choices],
|
|
[x[0] for x in c.fork_choices],
|
|
[x[0] for x in c.inherit_default_permission_choices],
|
|
)()
|
|
|
|
try:
|
|
form_result = _form.to_python(dict(self.request.POST))
|
|
form_result.update({"perm_user_name": User.DEFAULT_USER})
|
|
PermissionModel().update_user_permissions(form_result)
|
|
|
|
Session().commit()
|
|
h.flash(_("Global permissions updated successfully"), category="success")
|
|
|
|
except formencode.Invalid as errors:
|
|
defaults = errors.value
|
|
|
|
data = render(
|
|
"rhodecode:templates/admin/permissions/permissions.mako", self._get_template_context(c), self.request
|
|
)
|
|
html = formencode.htmlfill.render(
|
|
data,
|
|
defaults=defaults,
|
|
errors=errors.unpack_errors() or {},
|
|
prefix_error=False,
|
|
encoding="UTF-8",
|
|
force_defaults=False,
|
|
)
|
|
return Response(html)
|
|
except Exception:
|
|
log.exception("Exception during update of permissions")
|
|
h.flash(_("Error occurred during update of permissions"), category="error")
|
|
|
|
affected_user_ids = [User.get_default_user_id()]
|
|
PermissionModel().trigger_permission_flush(affected_user_ids)
|
|
|
|
raise HTTPFound(h.route_path("admin_permissions_global"))
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def permissions_ips(self):
|
|
c = self.load_default_context()
|
|
c.active = "ips"
|
|
|
|
c.user = User.get_default_user(refresh=True)
|
|
c.user_ip_map = UserIpMap.query().filter(UserIpMap.user == c.user).all()
|
|
|
|
return self._get_template_context(c)
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def permissions_overview(self):
|
|
c = self.load_default_context()
|
|
c.active = "perms"
|
|
|
|
c.user = User.get_default_user(refresh=True)
|
|
c.perm_user = c.user.AuthUser()
|
|
return self._get_template_context(c)
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def auth_token_access(self):
|
|
from rhodecode import CONFIG
|
|
|
|
c = self.load_default_context()
|
|
c.active = "auth_token_access"
|
|
|
|
c.user = User.get_default_user(refresh=True)
|
|
c.perm_user = c.user.AuthUser()
|
|
|
|
mapper = self.request.registry.queryUtility(IRoutesMapper)
|
|
c.view_data = []
|
|
|
|
_argument_prog = re.compile(r"\{(.*?)\}|:\((.*)\)")
|
|
introspector = self.request.registry.introspector
|
|
|
|
view_intr = {}
|
|
for view_data in introspector.get_category("views"):
|
|
intr = view_data["introspectable"]
|
|
|
|
if "route_name" in intr and intr["attr"]:
|
|
view_intr[intr["route_name"]] = "{}:{}".format(str(intr["derived_callable"].__name__), intr["attr"])
|
|
|
|
c.whitelist_key = "api_access_controllers_whitelist"
|
|
c.whitelist_file = CONFIG.get("__file__")
|
|
whitelist_views = aslist(CONFIG.get(c.whitelist_key), sep=",")
|
|
|
|
for route_info in mapper.get_routes():
|
|
if not route_info.name.startswith("__"):
|
|
routepath = route_info.pattern
|
|
|
|
def replace(matchobj):
|
|
if matchobj.group(1):
|
|
return "{%s}" % matchobj.group(1).split(":")[0]
|
|
else:
|
|
return "{%s}" % matchobj.group(2)
|
|
|
|
routepath = _argument_prog.sub(replace, routepath)
|
|
|
|
if not routepath.startswith("/"):
|
|
routepath = "/" + routepath
|
|
|
|
view_fqn = view_intr.get(route_info.name, "NOT AVAILABLE")
|
|
active = view_fqn in whitelist_views
|
|
c.view_data.append((route_info.name, view_fqn, routepath, active))
|
|
|
|
c.whitelist_views = whitelist_views
|
|
return self._get_template_context(c)
|
|
|
|
def ssh_enabled(self):
|
|
return self.request.registry.settings.get("ssh.generate_authorized_keyfile")
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def ssh_keys(self):
|
|
c = self.load_default_context()
|
|
c.active = "ssh_keys"
|
|
c.ssh_enabled = self.ssh_enabled()
|
|
return self._get_template_context(c)
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
def ssh_keys_data(self):
|
|
_ = self.request.translate
|
|
self.load_default_context()
|
|
column_map = {"fingerprint": "ssh_key_fingerprint", "username": User.username}
|
|
draw, start, limit = self._extract_chunk(self.request)
|
|
search_q, order_by, order_dir = self._extract_ordering(self.request, column_map=column_map)
|
|
|
|
ssh_keys_data_total_count = UserSshKeys.query().count()
|
|
|
|
# json generate
|
|
base_q = UserSshKeys.query().join(UserSshKeys.user)
|
|
|
|
if search_q:
|
|
like_expression = f"%{safe_str(search_q)}%"
|
|
base_q = base_q.filter(
|
|
or_(
|
|
User.username.ilike(like_expression),
|
|
UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
|
|
)
|
|
)
|
|
|
|
users_data_total_filtered_count = base_q.count()
|
|
|
|
sort_col = self._get_order_col(order_by, UserSshKeys)
|
|
if sort_col:
|
|
if order_dir == "asc":
|
|
# handle null values properly to order by NULL last
|
|
if order_by in ["created_on"]:
|
|
sort_col = coalesce(sort_col, datetime.date.max)
|
|
sort_col = sort_col.asc()
|
|
else:
|
|
# handle null values properly to order by NULL last
|
|
if order_by in ["created_on"]:
|
|
sort_col = coalesce(sort_col, datetime.date.min)
|
|
sort_col = sort_col.desc()
|
|
|
|
base_q = base_q.order_by(sort_col)
|
|
base_q = base_q.offset(start).limit(limit)
|
|
|
|
ssh_keys = base_q.all()
|
|
|
|
ssh_keys_data = []
|
|
for ssh_key in ssh_keys:
|
|
ssh_keys_data.append(
|
|
{
|
|
"username": h.gravatar_with_user(self.request, ssh_key.user.username),
|
|
"fingerprint": ssh_key.ssh_key_fingerprint,
|
|
"description": ssh_key.description,
|
|
"created_on": h.format_date(ssh_key.created_on),
|
|
"accessed_on": h.format_date(ssh_key.accessed_on),
|
|
"action": h.link_to(_("Edit"), h.route_path("edit_user_ssh_keys", user_id=ssh_key.user.user_id)),
|
|
}
|
|
)
|
|
|
|
data = {
|
|
"draw": draw,
|
|
"data": ssh_keys_data,
|
|
"recordsTotal": ssh_keys_data_total_count,
|
|
"recordsFiltered": users_data_total_filtered_count,
|
|
}
|
|
|
|
return data
|
|
|
|
@LoginRequired()
|
|
@HasPermissionAllDecorator("hg.admin")
|
|
@CSRFRequired()
|
|
def ssh_keys_update(self):
|
|
_ = self.request.translate
|
|
self.load_default_context()
|
|
|
|
ssh_enabled = self.ssh_enabled()
|
|
key_file = self.request.registry.settings.get("ssh.authorized_keys_file_path")
|
|
if ssh_enabled:
|
|
events.trigger(SshKeyFileChangeEvent(), self.request.registry)
|
|
h.flash(_("Updated SSH keys file: {}").format(key_file), category="success")
|
|
else:
|
|
h.flash(_("SSH key support is disabled in .ini file"), category="warning")
|
|
|
|
raise HTTPFound(h.route_path("admin_permissions_ssh_keys"))
|