Source code for pymapadmin.commands.user


from __future__ import annotations

import getpass
from argparse import ArgumentParser, FileType
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from typing import Any, TypeAlias

from .base import AdminCommand
from ..grpc.admin_grpc import UserStub
from ..grpc.admin_pb2 import \
    SUCCESS, UserData, UserResponse, GetUserRequest, SetUserRequest, \
    DeleteUserRequest
from ..operation import SingleOperation, CompoundOperation
from ..typing import AdminRequestT, AdminResponseT, MethodProtocol

__all__ = ['GetUserCommand', 'SetUserCommand', 'ChangePasswordCommand',
           'DeleteUserCommand']

_Password: TypeAlias = str | None


@dataclass(frozen=True)
class _ChangePasswordRequest:
    user: str
    password: _Password


class UserCommand(AdminCommand[UserStub, AdminRequestT, AdminResponseT]):

    @property
    def client(self) -> UserStub:
        return UserStub(self.channel)

    def getpass(self) -> _Password:
        if self.args.no_password:
            return None
        elif self.args.password_file:
            line: str = self.args.password_file.readline()
            return line.rstrip('\r\n')
        else:  # pragma: no cover
            return getpass.getpass()


[docs] class GetUserCommand(UserCommand[GetUserRequest, UserResponse], SingleOperation[GetUserRequest, UserResponse]): """Print a user and its metadata."""
[docs] @classmethod def add_subparser(cls, name: str, subparsers: Any) \ -> ArgumentParser: # pragma: no cover subparser: ArgumentParser = subparsers.add_parser( name, description=cls.__doc__, help='get a user') subparser.add_argument('user', help='the user name') return subparser
@property def method(self) -> MethodProtocol[GetUserRequest, UserResponse]: return self.client.GetUser
[docs] def build_request(self) -> GetUserRequest: return GetUserRequest(user=self.args.user)
[docs] class SetUserCommand(UserCommand[SetUserRequest, UserResponse], SingleOperation[SetUserRequest, UserResponse]): """Set the metadata for a user, creating it if it does not exist."""
[docs] @classmethod def add_subparser(cls, name: str, subparsers: Any) \ -> ArgumentParser: # pragma: no cover subparser: ArgumentParser = subparsers.add_parser( name, description=cls.__doc__, help='add or overwrite a user') subparser.add_argument('--no-overwrite', action='store_false', dest='overwrite', help='do not overwrite existing users') subparser.add_argument('--password-file', type=FileType('r'), metavar='FILE', help='read the password from a file') subparser.add_argument('--no-password', action='store_true', help='send the request with no password value') subparser.add_argument('--param', action='append', dest='params', default=[], metavar='KEY=VAL', help='additional parameters for the request') subparser.add_argument('--role', action='append', dest='roles', default=[], metavar='ROLE', help='assigned roles for the user') subparser.add_argument('user', help='the user name') return subparser
@property def method(self) -> MethodProtocol[SetUserRequest, UserResponse]: return self.client.SetUser
[docs] def build_request(self) -> SetUserRequest: args = self.args params = self._parse_params(args.params) password = self.getpass() new_data = UserData(params=params, roles=args.roles) if password is not None: new_data.password = password return SetUserRequest(user=args.user, overwrite=args.overwrite, data=new_data)
def _parse_params(self, params: Sequence[str]) -> Mapping[str, str]: ret = {} for param in params: key, splitter, val = param.partition('=') if not splitter: raise ValueError(f'Expected key=val format: {param!r}') ret[key] = val return ret
[docs] class ChangePasswordCommand(UserCommand[_ChangePasswordRequest, UserResponse], CompoundOperation[_ChangePasswordRequest, GetUserRequest, UserResponse, SetUserRequest, UserResponse]): """Change a password for an existing user, without modifying any other metadata. """
[docs] @classmethod def add_subparser(cls, name: str, subparsers: Any) \ -> ArgumentParser: # pragma: no cover subparser: ArgumentParser = subparsers.add_parser( name, description=cls.__doc__, help='assign a new password to a user') subparser.add_argument('--password-file', type=FileType('r'), metavar='FILE', help='read the password from a file') subparser.add_argument('--no-password', action='store_true', help='send the request with no password value') subparser.add_argument('user', help='the user name') return subparser
@property def first(self) -> GetUserCommand: return GetUserCommand(self.args, self.channel) @property def second(self) -> SetUserCommand: return SetUserCommand(self.args, self.channel)
[docs] def build_first(self, request: _ChangePasswordRequest) -> GetUserRequest: return GetUserRequest(user=request.user)
[docs] def build_second(self, request: _ChangePasswordRequest, first_response: UserResponse) \ -> tuple[SetUserRequest | None, UserResponse | None]: password = request.password if first_response.result.code != SUCCESS: return None, first_response user_data = first_response.data if password is None: user_data.ClearField('password') else: user_data.password = password second_request = SetUserRequest( user=request.user, previous_entity_tag=first_response.entity_tag, data=user_data) return second_request, None
[docs] def build_request(self) -> _ChangePasswordRequest: args = self.args password = self.getpass() return _ChangePasswordRequest(args.user, password)
[docs] class DeleteUserCommand(UserCommand[DeleteUserRequest, UserResponse], SingleOperation[DeleteUserRequest, UserResponse]): """Delete a user and its mail data."""
[docs] @classmethod def add_subparser(cls, name: str, subparsers: Any) \ -> ArgumentParser: # pragma: no cover subparser: ArgumentParser = subparsers.add_parser( name, description=cls.__doc__, help='delete a user') subparser.add_argument('user', help='the user name') return subparser
@property def method(self) -> MethodProtocol[DeleteUserRequest, UserResponse]: return self.client.DeleteUser
[docs] def build_request(self) -> DeleteUserRequest: return DeleteUserRequest(user=self.args.user)