##// END OF EJS Templates
docs: added newline
docs: added newline

File last commit:

r5608:6d33e504 default
r5656:629f48be default
Show More
user_api.py
570 lines | 18.7 KiB | text/x-python | PythonLexer
core: updated copyright to 2024
r5608 # Copyright (C) 2011-2024 RhodeCode GmbH
project: added all source files and assets
r1 #
# 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 logging
user-api: use simple schema validator to be consistent how we validate between API and web views.
r1832 from rhodecode.api import (
jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
project: added all source files and assets
r1 from rhodecode.api.utils import (
Optional, OAttr, has_superadmin_permission, get_user_or_error, store_update)
audit-logs: implemented full audit logs across application....
r1829 from rhodecode.lib import audit_logger
project: added all source files and assets
r1 from rhodecode.lib.auth import AuthUser, PasswordGenerator
from rhodecode.lib.exceptions import DefaultUserException
repo-groups: implemented default personal repo groups logic....
r1094 from rhodecode.lib.utils2 import safe_int, str2bool
project: added all source files and assets
r1 from rhodecode.model.db import Session, User, Repository
from rhodecode.model.user import UserModel
user-api: use simple schema validator to be consistent how we validate between API and web views.
r1832 from rhodecode.model import validation_schema
from rhodecode.model.validation_schema.schemas import user_schema
project: added all source files and assets
r1
log = logging.getLogger(__name__)
@jsonrpc_method()
def get_user(request, apiuser, userid=Optional(OAttr('apiuser'))):
"""
Returns the information associated with a username or userid.
* If the ``userid`` is not set, this command returns the information
for the ``userid`` calling the method.
.. note::
Normal users may only run this command against their ``userid``. For
full privileges you must run this command using an |authtoken| with
admin rights.
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param userid: Sets the userid for which data will be returned.
:type userid: Optional(str or int)
Example output:
.. code-block:: bash
{
"error": null,
"id": <id>,
"result": {
"active": true,
"admin": false,
"api_keys": [ list of keys ],
api: expose more details of auth tokens in get_user api call.
r1522 "auth_tokens": [ list of tokens with details ],
project: added all source files and assets
r1 "email": "user@example.com",
"emails": [
"user@example.com"
],
"extern_name": "rhodecode",
"extern_type": "rhodecode",
"firstname": "username",
users: description edit fixes...
r4024 "description": "user description",
project: added all source files and assets
r1 "ip_addresses": [],
"language": null,
"last_login": "Timestamp",
api: added last-activity into returned data of get_user api....
r1558 "last_activity": "Timestamp",
project: added all source files and assets
r1 "lastname": "surnae",
api: fixed tests and update docstrings.
r2438 "permissions": <deprecated>,
"permissions_summary": {
project: added all source files and assets
r1 "global": [
"hg.inherit_default_perms.true",
"usergroup.read",
"hg.repogroup.create.false",
"hg.create.none",
settings: fix #3944 add password reset permission
r1034 "hg.password_reset.enabled",
project: added all source files and assets
r1 "hg.extern_activate.manual",
"hg.create.write_on_repogroup.false",
"hg.usergroup.create.false",
"group.none",
"repository.none",
"hg.register.none",
"hg.fork.repository"
],
"repositories": { "username/example": "repository.write"},
"repositories_groups": { "user-group/repo": "group.none" },
"user_groups": { "user_group_name": "usergroup.read" }
api: fixed tests and update docstrings.
r2438 }
project: added all source files and assets
r1 "user_id": 32,
"username": "username"
}
}
"""
if not has_superadmin_permission(apiuser):
# make sure normal user does not pass someone else userid,
# he is not allowed to do that
if not isinstance(userid, Optional) and userid != apiuser.user_id:
raise JSONRPCError('userid is not the same as your user')
userid = Optional.extract(userid, evaluate_locals=locals())
userid = getattr(userid, 'user_id', userid)
user = get_user_or_error(userid)
data = user.get_api_data(include_secrets=True)
api: add consistent permissions_summary data for both user and user_groups that expose...
r2437 permissions = AuthUser(user_id=user.user_id).permissions
data['permissions'] = permissions # TODO(marcink): should be deprecated
data['permissions_summary'] = permissions
project: added all source files and assets
r1 return data
@jsonrpc_method()
def get_users(request, apiuser):
"""
Lists all users in the |RCE| user database.
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 : <id_given_in_input>
docs: update API documentation.
r1603 result: [<user_object>, ...]
project: added all source files and assets
r1 error: null
"""
if not has_superadmin_permission(apiuser):
raise JSONRPCForbidden()
result = []
users_list = User.query().order_by(User.username) \
.filter(User.username != User.DEFAULT_USER) \
.all()
for user in users_list:
result.append(user.get_api_data(include_secrets=True))
return result
@jsonrpc_method()
def create_user(request, apiuser, username, email, password=Optional(''),
users: description edit fixes...
r4024 firstname=Optional(''), lastname=Optional(''), description=Optional(''),
project: added all source files and assets
r1 active=Optional(True), admin=Optional(False),
extern_name=Optional('rhodecode'),
extern_type=Optional('rhodecode'),
repo-groups: implemented default personal repo groups logic....
r1094 force_password_change=Optional(False),
create_personal_repo_group=Optional(None)):
project: added all source files and assets
r1 """
Creates a new user and returns the new user object.
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 username: Set the new username.
:type username: str or int
:param email: Set the user email address.
:type email: str
:param password: Set the new user password.
:type password: Optional(str)
:param firstname: Set the new user firstname.
:type firstname: Optional(str)
:param lastname: Set the new user surname.
:type lastname: Optional(str)
users: description edit fixes...
r4024 :param description: Set user description, or short bio. Metatags are allowed.
:type description: Optional(str)
project: added all source files and assets
r1 :param active: Set the user as active.
:type active: Optional(``True`` | ``False``)
:param admin: Give the new user admin rights.
:type admin: Optional(``True`` | ``False``)
:param extern_name: Set the authentication plugin name.
Using LDAP this is filled with LDAP UID.
:type extern_name: Optional(str)
:param extern_type: Set the new user authentication plugin.
:type extern_type: Optional(str)
:param force_password_change: Force the new user to change password
on next login.
:type force_password_change: Optional(``True`` | ``False``)
repo-groups: implemented default personal repo groups logic....
r1094 :param create_personal_repo_group: Create personal repo group for this user
:type create_personal_repo_group: Optional(``True`` | ``False``)
docs: update API documentation.
r1603
project: added all source files and assets
r1 Example output:
.. code-block:: bash
id : <id_given_in_input>
result: {
docs: update API documentation.
r1603 "msg" : "created new user `<username>`",
"user": <user_obj>
}
project: added all source files and assets
r1 error: null
Example error output:
.. code-block:: bash
id : <id_given_in_input>
result : null
error : {
"user `<username>` already exist"
or
"email `<email>` already exist"
or
"failed to create user `<username>`"
}
"""
if not has_superadmin_permission(apiuser):
raise JSONRPCForbidden()
if UserModel().get_by_username(username):
modernize: updates for python3
r5095 raise JSONRPCError(f"user `{username}` already exist")
project: added all source files and assets
r1
if UserModel().get_by_email(email, case_insensitive=True):
modernize: updates for python3
r5095 raise JSONRPCError(f"email `{email}` already exist")
project: added all source files and assets
r1
# generate random password if we actually given the
# extern_name and it's not rhodecode
if (not isinstance(extern_name, Optional) and
Optional.extract(extern_name) != 'rhodecode'):
# generate temporary password if user is external
password = PasswordGenerator().gen_password(length=16)
repo-groups: implemented default personal repo groups logic....
r1094 create_repo_group = Optional.extract(create_personal_repo_group)
py3: remove use of pyramid.compat
r4908 if isinstance(create_repo_group, str):
repo-groups: implemented default personal repo groups logic....
r1094 create_repo_group = str2bool(create_repo_group)
project: added all source files and assets
r1
user-api: use simple schema validator to be consistent how we validate between API and web views.
r1832 username = Optional.extract(username)
password = Optional.extract(password)
email = Optional.extract(email)
first_name = Optional.extract(firstname)
last_name = Optional.extract(lastname)
users: description edit fixes...
r4024 description = Optional.extract(description)
user-api: use simple schema validator to be consistent how we validate between API and web views.
r1832 active = Optional.extract(active)
admin = Optional.extract(admin)
extern_type = Optional.extract(extern_type)
extern_name = Optional.extract(extern_name)
schema = user_schema.UserSchema().bind(
# user caller
user=apiuser)
try:
schema_data = schema.deserialize(dict(
username=username,
email=email,
password=password,
first_name=first_name,
last_name=last_name,
active=active,
admin=admin,
users: description edit fixes...
r4024 description=description,
user-api: use simple schema validator to be consistent how we validate between API and web views.
r1832 extern_type=extern_type,
extern_name=extern_name,
))
except validation_schema.Invalid as err:
raise JSONRPCValidationError(colander_exc=err)
project: added all source files and assets
r1 try:
user = UserModel().create_or_update(
user-api: use simple schema validator to be consistent how we validate between API and web views.
r1832 username=schema_data['username'],
password=schema_data['password'],
email=schema_data['email'],
firstname=schema_data['first_name'],
lastname=schema_data['last_name'],
users: description edit fixes...
r4024 description=schema_data['description'],
user-api: use simple schema validator to be consistent how we validate between API and web views.
r1832 active=schema_data['active'],
admin=schema_data['admin'],
extern_type=schema_data['extern_type'],
extern_name=schema_data['extern_name'],
project: added all source files and assets
r1 force_password_change=Optional.extract(force_password_change),
repo-groups: implemented default personal repo groups logic....
r1094 create_repo_group=create_repo_group
project: added all source files and assets
r1 )
audit-logs: implemented full audit logs across application....
r1829 Session().flush()
creation_data = user.get_api_data()
audit_logger.store_api(
'user.create', action_data={'data': creation_data},
user=apiuser)
project: added all source files and assets
r1 Session().commit()
return {
'msg': 'created new user `%s`' % username,
'user': user.get_api_data(include_secrets=True)
}
except Exception:
log.exception('Error occurred during creation of user')
modernize: updates for python3
r5095 raise JSONRPCError(f'failed to create user `{username}`')
project: added all source files and assets
r1
@jsonrpc_method()
def update_user(request, apiuser, userid, username=Optional(None),
email=Optional(None), password=Optional(None),
firstname=Optional(None), lastname=Optional(None),
users: description edit fixes...
r4024 description=Optional(None), active=Optional(None), admin=Optional(None),
project: added all source files and assets
r1 extern_type=Optional(None), extern_name=Optional(None), ):
"""
Updates the details for the specified user, if that user exists.
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: Set the ``userid`` to update.
:type userid: str or int
:param username: Set the new username.
:type username: str or int
:param email: Set the new email.
:type email: str
:param password: Set the new password.
:type password: Optional(str)
:param firstname: Set the new first name.
:type firstname: Optional(str)
:param lastname: Set the new surname.
:type lastname: Optional(str)
users: description edit fixes...
r4024 :param description: Set user description, or short bio. Metatags are allowed.
:type description: Optional(str)
project: added all source files and assets
r1 :param active: Set the new user as active.
:type active: Optional(``True`` | ``False``)
:param admin: Give the user admin rights.
:type admin: Optional(``True`` | ``False``)
:param extern_name: Set the authentication plugin user name.
Using LDAP this is filled with LDAP UID.
:type extern_name: Optional(str)
:param extern_type: Set the authentication plugin type.
:type extern_type: Optional(str)
Example output:
.. code-block:: bash
id : <id_given_in_input>
result: {
docs: update API documentation.
r1603 "msg" : "updated user ID:<userid> <username>",
"user": <user_object>,
}
project: added all source files and assets
r1 error: null
Example error output:
.. code-block:: bash
id : <id_given_in_input>
result : null
error : {
"failed to update user `<username>`"
}
"""
if not has_superadmin_permission(apiuser):
raise JSONRPCForbidden()
user = get_user_or_error(userid)
audit-logs: implemented full audit logs across application....
r1829 old_data = user.get_api_data()
project: added all source files and assets
r1 # only non optional arguments will be stored in updates
updates = {}
try:
store_update(updates, username, 'username')
store_update(updates, password, 'password')
store_update(updates, email, 'email')
store_update(updates, firstname, 'name')
store_update(updates, lastname, 'lastname')
users: description edit fixes...
r4024 store_update(updates, description, 'description')
project: added all source files and assets
r1 store_update(updates, active, 'active')
store_update(updates, admin, 'admin')
store_update(updates, extern_name, 'extern_name')
store_update(updates, extern_type, 'extern_type')
user = UserModel().update_user(user, **updates)
audit-logs: implemented full audit logs across application....
r1829 audit_logger.store_api(
'user.edit', action_data={'old_data': old_data},
user=apiuser)
project: added all source files and assets
r1 Session().commit()
return {
modernize: updates for python3
r5095 'msg': f'updated user ID:{user.user_id} {user.username}',
project: added all source files and assets
r1 'user': user.get_api_data(include_secrets=True)
}
except DefaultUserException:
log.exception("Default user edit exception")
raise JSONRPCError('editing default user is forbidden')
except Exception:
log.exception("Error occurred during update of user")
modernize: updates for python3
r5095 raise JSONRPCError(f'failed to update user `{userid}`')
project: added all source files and assets
r1
@jsonrpc_method()
def delete_user(request, apiuser, userid):
"""
Deletes the specified user from the |RCE| user database.
This command can only be run using an |authtoken| with admin rights to
the specified repository.
.. important::
Ensure all open pull requests and open code review
requests to this user are close.
Also ensure all repositories, or repository groups owned by this
user are reassigned before deletion.
This command takes the following options:
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param userid: Set the user to delete.
:type userid: str or int
Example output:
.. code-block:: bash
id : <id_given_in_input>
result: {
docs: update API documentation.
r1603 "msg" : "deleted user ID:<userid> <username>",
"user": null
}
project: added all source files and assets
r1 error: null
Example error output:
.. code-block:: bash
id : <id_given_in_input>
result : null
error : {
"failed to delete user ID:<userid> <username>"
}
"""
if not has_superadmin_permission(apiuser):
raise JSONRPCForbidden()
user = get_user_or_error(userid)
audit-logs: implemented full audit logs across application....
r1829 old_data = user.get_api_data()
project: added all source files and assets
r1 try:
UserModel().delete(userid)
audit-logs: implemented full audit logs across application....
r1829 audit_logger.store_api(
'user.delete', action_data={'old_data': old_data},
user=apiuser)
project: added all source files and assets
r1 Session().commit()
return {
modernize: updates for python3
r5095 'msg': f'deleted user ID:{user.user_id} {user.username}',
project: added all source files and assets
r1 'user': None
}
except Exception:
log.exception("Error occurred during deleting of user")
raise JSONRPCError(
modernize: updates for python3
r5095 f'failed to delete user ID:{user.user_id} {user.username}')
project: added all source files and assets
r1
@jsonrpc_method()
def get_user_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
"""
Displays all repositories locked by the specified user.
* If this command is run by a non-admin user, it returns
a list of |repos| locked by that user.
This command takes the following options:
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param userid: Sets the userid whose list of locked |repos| will be
displayed.
:type userid: Optional(str or int)
Example output:
.. code-block:: bash
id : <id_given_in_input>
result : {
[repo_object, repo_object,...]
}
error : null
"""
include_secrets = False
if not has_superadmin_permission(apiuser):
# make sure normal user does not pass someone else userid,
# he is not allowed to do that
if not isinstance(userid, Optional) and userid != apiuser.user_id:
raise JSONRPCError('userid is not the same as your user')
else:
include_secrets = True
userid = Optional.extract(userid, evaluate_locals=locals())
userid = getattr(userid, 'user_id', userid)
user = get_user_or_error(userid)
ret = []
# show all locks
for r in Repository.getAll():
_user_id, _time, _reason = r.locked
if _user_id and _time:
_api_data = r.get_api_data(include_secrets=include_secrets)
# if we use user filter just show the locks for this user
if safe_int(_user_id) == user.user_id:
ret.append(_api_data)
return ret
user-api: enable per-user audit logs fetching via API endpoint.
r1579
@jsonrpc_method()
def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
"""
Fetches all action logs made by the specified user.
This command takes the following options:
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param userid: Sets the userid whose list of locked |repos| will be
displayed.
:type userid: Optional(str or int)
Example output:
.. code-block:: bash
id : <id_given_in_input>
result : {
[action, action,...]
}
error : null
"""
if not has_superadmin_permission(apiuser):
# make sure normal user does not pass someone else userid,
# he is not allowed to do that
if not isinstance(userid, Optional) and userid != apiuser.user_id:
raise JSONRPCError('userid is not the same as your user')
userid = Optional.extract(userid, evaluate_locals=locals())
userid = getattr(userid, 'user_id', userid)
user = get_user_or_error(userid)
ret = []
# show all user actions
for entry in UserModel().get_user_log(user, filter_term=None):
ret.append(entry)
return ret