public inbox for pmg-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Stoiko Ivanov <s.ivanov@proxmox.com>
To: Markus Frank <m.frank@proxmox.com>
Cc: pmg-devel@lists.proxmox.com
Subject: Re: [pmg-devel] [PATCH pmg-api v8 3/13] config: add plugin system for authentication realms
Date: Wed, 26 Feb 2025 15:40:15 +0100	[thread overview]
Message-ID: <20250226154015.1004f27c@rosa.proxmox.com> (raw)
In-Reply-To: <20250226140740.55612-4-m.frank@proxmox.com>

While trying to add the realm config to the cluster-sync I noticed a small
mismatch (cfg vs. conf):
* user.conf, ldap.conf vs. realms.cfg
While its mostly cosmetic - now would be an easy time to change
this if we want to have it aligned (w/o version-guarded copying in
maintscripts)

probably can be fixed up on applying if nothing else comes up
(occurences should be here and in your 6/13 (will reply there as well))

the places where a rename has to happen as comments inline:
On Wed, 26 Feb 2025 15:07:30 +0100
Markus Frank <m.frank@proxmox.com> wrote:

> To differentiate between usernames, the realm is also stored in the
> user.conf file. Old config file syntax can be read, but will be
> overwritten after a change.
> 
> This is a carryover from PVE. Previously there was no realm for a
> username. Now the realm is also stored after an @ sign in user.conf.
> 
> Utils generates a list of valid realm names, including any newly added
> realms, to ensure proper validation of a specified realm name.
> 
> Signed-off-by: Markus Frank <m.frank@proxmox.com>
> ---
>  src/Makefile               |   3 +
>  src/PMG/API2/Users.pm      |   1 +
>  src/PMG/AccessControl.pm   |  38 +++++++
>  src/PMG/Auth/PAM.pm        |  21 ++++
>  src/PMG/Auth/PMG.pm        |  37 +++++++
>  src/PMG/Auth/Plugin.pm     | 202 +++++++++++++++++++++++++++++++++++++
>  src/PMG/RESTEnvironment.pm |  14 +++
>  src/PMG/UserConfig.pm      |  25 +++--
>  src/PMG/Utils.pm           |  29 ++++--
>  9 files changed, 354 insertions(+), 16 deletions(-)
>  create mode 100755 src/PMG/Auth/PAM.pm
>  create mode 100755 src/PMG/Auth/PMG.pm
>  create mode 100755 src/PMG/Auth/Plugin.pm
> 
> diff --git a/src/Makefile b/src/Makefile
> index 1232880..659a666 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -166,6 +166,9 @@ LIBSOURCES =				\
>  	PMG/API2/ACMEPlugin.pm		\
>  	PMG/API2/NodeConfig.pm		\
>  	PMG/API2.pm			\
> +	PMG/Auth/Plugin.pm		\
> +	PMG/Auth/PAM.pm			\
> +	PMG/Auth/PMG.pm			\
>  
>  SOURCES = ${LIBSOURCES} ${CLI_BINARIES} ${TEMPLATES_FILES} ${CONF_MANS} ${CLI_MANS} ${SERVICE_MANS} ${SERVICE_UNITS} ${TIMER_UNITS} pmg-sources.list pmg-initramfs.conf
>  
> diff --git a/src/PMG/API2/Users.pm b/src/PMG/API2/Users.pm
> index 99af38d..42c82b7 100644
> --- a/src/PMG/API2/Users.pm
> +++ b/src/PMG/API2/Users.pm
> @@ -124,6 +124,7 @@ __PACKAGE__->register_method ({
>  	    $entry->{enable} //= 0;
>  	    $entry->{expire} //= 0;
>  	    $entry->{role} //= 'audit';
> +	    $entry->{realm} //= 'pmg';
>  
>  	    $cfg->{$param->{userid}} = $entry;
>  
> diff --git a/src/PMG/AccessControl.pm b/src/PMG/AccessControl.pm
> index e12d7cf..78105c0 100644
> --- a/src/PMG/AccessControl.pm
> +++ b/src/PMG/AccessControl.pm
> @@ -5,6 +5,7 @@ use warnings;
>  use Authen::PAM;
>  
>  use PVE::Tools;
> +use PVE::INotify;
>  use PVE::JSONSchema qw(get_standard_option);
>  use PVE::Exception qw(raise raise_perm_exc);
>  
> @@ -13,6 +14,14 @@ use PMG::LDAPConfig;
>  use PMG::LDAPSet;
>  use PMG::TFAConfig;
>  
> +use PMG::Auth::Plugin;
> +use PMG::Auth::PAM;
> +use PMG::Auth::PMG;
> +
> +PMG::Auth::PAM->register();
> +PMG::Auth::PMG->register();
> +PMG::Auth::Plugin->init();
> +
>  sub normalize_path {
>      my $path = shift;
>  
> @@ -38,6 +47,7 @@ sub authenticate_user : prototype($$$) {
>  
>      ($username, $ruid, $realm) = PMG::Utils::verify_username($username);
>  
> +    my $realm_regex = PMG::Utils::valid_pmg_realm_regex();
>      if ($realm eq 'pam') {
>  	die "invalid pam user (only root allowed)\n" if $ruid ne 'root';
>  	authenticate_pam_user($ruid, $password);
> @@ -53,6 +63,11 @@ sub authenticate_user : prototype($$$) {
>  	    return ($pmail . '@quarantine', undef);
>  	}
>  	die "ldap login failed\n";
> +    } elsif ($realm =~ m!(${realm_regex})!) {
> +	my $realm_cfg = PVE::INotify::read_file(PMG::Auth::Plugin->realm_cfg_id());
> +	my $cfg = $realm_cfg->{ids}->{$realm};
> +	my $plugin = PMG::Auth::Plugin->lookup($cfg->{type});
> +	$plugin->authenticate_user($cfg, $realm, $ruid, $password);
>      } else {
>  	die "no such realm '$realm'\n";
>      }
> @@ -79,6 +94,7 @@ sub set_user_password {
>      
>      ($username, $ruid, $realm) = PMG::Utils::verify_username($username);
>  
> +    my $realm_regex = PMG::Utils::valid_pmg_realm_regex();
>      if ($realm eq 'pam') {
>  	die "invalid pam user (only root allowed)\n" if $ruid ne 'root';
>  
> @@ -92,6 +108,11 @@ sub set_user_password {
>  
>      } elsif ($realm eq 'pmg') {
>  	PMG::UserConfig->set_user_password($username, $password);
> +    } elsif ($realm =~ m!(${realm_regex})!) {
> +	my $realm_cfg = PVE::INotify::read_file(PMG::Auth::Plugin->realm_cfg_id());
> +	my $cfg = $realm_cfg->{ids}->{$realm};
> +	my $plugin = PMG::Auth::Plugin->lookup($cfg->{type});
> +	$plugin->store_password($cfg, $realm, $username, $password);
>      } else {
>  	die "no such realm '$realm'\n";
>      }
> @@ -106,6 +127,7 @@ sub check_user_enabled {
>  
>      ($username, $ruid, $realm) = PMG::Utils::verify_username($username, 1);
>  
> +    my $realm_regex = PMG::Utils::valid_pmg_realm_regex();
>      if ($realm && $ruid) {
>  	if ($realm eq 'pam') {
>  	    return 'root' if $ruid eq 'root';
> @@ -115,6 +137,10 @@ sub check_user_enabled {
>  	    return $data->{role} if $data && $data->{enable};
>  	} elsif ($realm eq 'quarantine') {
>  	    return 'quser';
> +	} elsif ($realm =~ m!(${realm_regex})!) {
> +	    my $usercfg = PMG::UserConfig->new();
> +	    my $data = $usercfg->lookup_user_data($username, $noerr);
> +	    return $data->{role} if $data && $data->{enable};
>  	}
>      }
>  
> @@ -123,6 +149,18 @@ sub check_user_enabled {
>      return undef;
>  }
>  
> +sub check_user_exist {
> +    my ($usercfg, $username, $noerr) = @_;
> +
> +    $username = PMG::Utils::verify_username($username, $noerr);
> +    return undef if !$username;
> +    return $usercfg->{$username} if $usercfg && $usercfg->{$username};
> +
> +    die "no such user ('$username')\n" if !$noerr;
> +
> +    return undef;
> +}
> +
>  sub authenticate_pam_user {
>      my ($username, $password) = @_;
>  
> diff --git a/src/PMG/Auth/PAM.pm b/src/PMG/Auth/PAM.pm
> new file mode 100755
> index 0000000..002c8e1
> --- /dev/null
> +++ b/src/PMG/Auth/PAM.pm
> @@ -0,0 +1,21 @@
> +package PMG::Auth::PAM;
> +
> +use strict;
> +use warnings;
> +
> +use PMG::Auth::Plugin;
> +
> +use base qw(PMG::Auth::Plugin);
> +
> +sub type {
> +    return 'pam';
> +}
> +
> +sub options {
> +    return {
> +	default => { optional => 1 },
> +	comment => { optional => 1 },
> +    };
> +}
> +
> +1;
> diff --git a/src/PMG/Auth/PMG.pm b/src/PMG/Auth/PMG.pm
> new file mode 100755
> index 0000000..0706c97
> --- /dev/null
> +++ b/src/PMG/Auth/PMG.pm
> @@ -0,0 +1,37 @@
> +package PMG::Auth::PMG;
> +
> +use strict;
> +use warnings;
> +
> +use PMG::Auth::Plugin;
> +
> +use base qw(PMG::Auth::Plugin);
> +
> +sub type {
> +    return 'pmg';
> +}
> +
> +sub properties {
> +    return {
> +	default => {
> +	    description => "Use this as default realm",
> +	    type => 'boolean',
> +	    optional => 1,
> +	},
> +	comment => {
> +	    description => "Description.",
> +	    type => 'string',
> +	    optional => 1,
> +	    maxLength => 4096,
> +	},
> +    };
> +}
> +
> +sub options {
> +    return {
> +	default => { optional => 1 },
> +	comment => { optional => 1 },
> +    };
> +}
> +
> +1;
> diff --git a/src/PMG/Auth/Plugin.pm b/src/PMG/Auth/Plugin.pm
> new file mode 100755
> index 0000000..beb8fc4
> --- /dev/null
> +++ b/src/PMG/Auth/Plugin.pm
> @@ -0,0 +1,202 @@
> +package PMG::Auth::Plugin;
> +
> +use strict;
> +use warnings;
> +
> +use Digest::SHA;
> +use Encode;
> +
> +use PMG::Utils;
> +use PVE::INotify;
> +use PVE::JSONSchema qw(get_standard_option);
> +use PVE::SectionConfig;
> +use PVE::Tools;
> +
> +use base qw(PVE::SectionConfig);
> +
> +my $realm_cfg_id = "realms.cfg";
here (else it's even more confusing)
> +my $lockfile = "/var/lock/pmg-realms.lck";
> +
> +sub realm_cfg_id {
> +    return $realm_cfg_id;
> +}
> +
> +sub read_realms_conf {
> +    my ($filename, $fh) = @_;
> +
> +    my $raw;
> +    $raw = do { local $/ = undef; <$fh> } if defined($fh);
> +
> +    return PMG::Auth::Plugin->parse_config($filename, $raw);
> +}
> +
> +sub write_realms_conf {
> +    my ($filename, $fh, $cfg) = @_;
> +
> +    my $raw = PMG::Auth::Plugin->write_config($filename, $cfg);
> +
> +    PVE::Tools::safe_print($filename, $fh, $raw);
> +}
> +
> +PVE::INotify::register_file(
> +    $realm_cfg_id,
> +    "/etc/pmg/realms.cfg",
here

> +    \&read_realms_conf,
> +    \&write_realms_conf,
> +    undef,
> +    always_call_parser => 1,
> +);
> +
> +sub lock_realm_config {
> +    my ($code, $errmsg) = @_;
> +
> +    PVE::Tools::lock_file($lockfile, undef, $code);
> +    if (my $err = $@) {
> +	$errmsg ? die "$errmsg: $err" : die $err;
> +    }
> +}
> +
> +my $realm_regex = qr/[A-Za-z][A-Za-z0-9\.\-_]+/;
> +
> +sub pmg_verify_realm {
> +    my ($realm, $noerr) = @_;
> +
> +    if ($realm !~ m/^${realm_regex}$/) {
> +	return undef if $noerr;
> +	die "value does not look like a valid realm\n";
> +    }
> +    return $realm;
> +}
> +
> +my $defaultData = {
> +    propertyList => {
> +	type => { description => "Realm type." },
> +	realm => get_standard_option('realm'),
> +    },
> +};
> +
> +sub private {
> +    return $defaultData;
> +}
> +
> +sub parse_section_header {
> +    my ($class, $line) = @_;
> +
> +    if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
> +	my ($type, $realm) = (lc($1), $2);
> +	my $errmsg = undef; # set if you want to skip whole section
> +	eval { pmg_verify_realm($realm); };
> +	$errmsg = $@ if $@;
> +	my $config = {}; # to return additional attributes
> +	return ($type, $realm, $errmsg, $config);
> +    }
> +    return undef;
> +}
> +
> +sub parse_config {
> +    my ($class, $filename, $raw) = @_;
> +
> +    my $cfg = $class->SUPER::parse_config($filename, $raw);
> +
> +    my $default;
> +    foreach my $realm (keys %{$cfg->{ids}}) {
> +	my $data = $cfg->{ids}->{$realm};
> +	# make sure there is only one default marker
> +	if ($data->{default}) {
> +	    if ($default) {
> +		delete $data->{default};
> +	    } else {
> +		$default = $realm;
> +	    }
> +	}
> +
> +	if ($data->{comment}) {
> +	    $data->{comment} = PVE::Tools::decode_text($data->{comment});
> +	}
> +
> +    }
> +
> +    # add default realms
> +    $cfg->{ids}->{pmg}->{type} = 'pmg'; # force type
> +    $cfg->{ids}->{pmg}->{comment} = "Proxmox Mail Gateway authentication server"
> +	if !$cfg->{ids}->{pmg}->{comment};
> +    $cfg->{ids}->{pmg}->{default} = 1
> +	if !$cfg->{ids}->{pmg}->{default};
> +
> +    $cfg->{ids}->{pam}->{type} = 'pam'; # force type
> +    $cfg->{ids}->{pam}->{comment} = "Linux PAM standard authentication"
> +	if !$cfg->{ids}->{pam}->{comment};
> +
> +    return $cfg;
> +};
> +
> +sub write_config {
> +    my ($class, $filename, $cfg) = @_;
> +
> +    foreach my $realm (keys %{$cfg->{ids}}) {
> +	my $data = $cfg->{ids}->{$realm};
> +	if ($data->{comment}) {
> +	    $data->{comment} = PVE::Tools::encode_text($data->{comment});
> +	}
> +    }
> +
> +    $class->SUPER::write_config($filename, $cfg);
> +}
> +
> +sub authenticate_user {
> +    my ($class, $config, $realm, $username, $password) = @_;
> +
> +    die "overwrite me";
> +}
> +
> +sub store_password {
> +    my ($class, $config, $realm, $username, $password) = @_;
> +
> +    my $type = $class->type();
> +
> +    die "can't set password on auth type '$type'\n";
> +}
> +
> +sub delete_user {
> +    my ($class, $config, $realm, $username) = @_;
> +
> +    # do nothing by default
> +}
> +
> +# called during addition of realm (before the new realm config got written)
> +# `password` is moved to %param to avoid writing it out to the config
> +# die to abort addition if there are (grave) problems
> +# NOTE: runs in a realm config *locked* context
> +sub on_add_hook {
> +    my ($class, $realm, $config, %param) = @_;
> +    # do nothing by default
> +}
> +
> +# called during realm configuration update (before the updated realm config got
> +# written). `password` is moved to %param to avoid writing it out to the config
> +# die to abort the update if there are (grave) problems
> +# NOTE: runs in a realm config *locked* context
> +sub on_update_hook {
> +    my ($class, $realm, $config, %param) = @_;
> +    # do nothing by default
> +}
> +
> +# called during deletion of realms (before the new realm config got written)
> +# and if the activate check on addition fails, to cleanup all storage traces
> +# which on_add_hook may have created.
> +# die to abort deletion if there are (very grave) problems
> +# NOTE: runs in a realm config *locked* context
> +sub on_delete_hook {
> +    my ($class, $realm, $config) = @_;
> +    # do nothing by default
> +}
> +
> +# called during addition and updates of realms (before the new realm config gets written)
> +# die to abort addition/update in case the connection/bind fails
> +# NOTE: runs in a realm config *locked* context
> +sub check_connection {
> +    my ($class, $realm, $config, %param) = @_;
> +    # do nothing by default
> +}
> +
> +1;
> diff --git a/src/PMG/RESTEnvironment.pm b/src/PMG/RESTEnvironment.pm
> index 3875720..f6ff449 100644
> --- a/src/PMG/RESTEnvironment.pm
> +++ b/src/PMG/RESTEnvironment.pm
> @@ -88,6 +88,20 @@ sub get_role {
>      return $self->{role};
>  }
>  
> +sub check_user_enabled {
> +    my ($self, $user, $noerr) = @_;
> +
> +    my $cfg = $self->{usercfg};
> +    return PMG::AccessControl::check_user_enabled($cfg, $user, $noerr);
> +}
> +
> +sub check_user_exist {
> +    my ($self, $user, $noerr) = @_;
> +
> +    my $cfg = $self->{usercfg};
> +    return PMG::AccessControl::check_user_exist($cfg, $user, $noerr);
> +}
> +
>  sub check_node_is_master {
>      my ($self, $noerr);
>  
> diff --git a/src/PMG/UserConfig.pm b/src/PMG/UserConfig.pm
> index b9a83a7..fe6d2c8 100644
> --- a/src/PMG/UserConfig.pm
> +++ b/src/PMG/UserConfig.pm
> @@ -80,7 +80,7 @@ my $schema = {
>  	realm => {
>  	    description => "Authentication realm.",
>  	    type => 'string',
> -	    enum => ['pam', 'pmg'],
> +	    format => 'pmg-realm',
>  	    default => 'pmg',
>  	    optional => 1,
>  	},
> @@ -219,10 +219,13 @@ sub read_user_conf {
>                 (?<keys>(?:[^:]*)) :
>                 $/x
>  	    ) {
> +		my @username_parts = split('@', $+{userid});
> +		my $username = $username_parts[0];
> +		my $realm = defined($username_parts[1]) ? $username_parts[1] : "pmg";
>  		my $d = {
> -		    username => $+{userid},
> -		    userid => $+{userid} . '@pmg',
> -		    realm => 'pmg',
> +		    username => $username,
> +		    userid => $username . '@' . $realm,
> +		    realm => $realm,
>  		    enable => $+{enable} || 0,
>  		    expire => $+{expire} || 0,
>  		    role => $+{role},
> @@ -235,8 +238,9 @@ sub read_user_conf {
>  		eval {
>  		    $verify_entry->($d);
>  		    $cfg->{$d->{userid}} = $d;
> -		    die "role 'root' is reserved\n"
> -			if $d->{role} eq 'root' && $d->{userid} ne 'root@pmg';
> +		    if ($d->{role} eq 'root' && $d->{userid} !~ /^root@(pmg|pam)$/) {
> +			die "role 'root' is reserved\n";
> +		    }
>  		};
>  		if (my $err = $@) {
>  		    warn "$filename: $err";
> @@ -274,22 +278,23 @@ sub write_user_conf {
>  	$verify_entry->($d);
>  	$cfg->{$d->{userid}} = $d;
>  
> +	my $realm_regex = PMG::Utils::valid_pmg_realm_regex();
>  	if ($d->{userid} ne 'root@pam') {
>  	    die "role 'root' is reserved\n" if $d->{role} eq 'root';
>  	    die "unable to add users for realm '$d->{realm}'\n"
> -		if $d->{realm} && $d->{realm} ne 'pmg';
> +		if $d->{realm} && $d->{realm} !~ m!(${realm_regex})!;
>  	}
>  
>  	my $line;
>  
>  	if ($userid eq 'root@pam') {
> -	    $line = 'root:';
> +	    $line = 'root@pam:';
>  	    $d->{crypt_pass} = '',
>  	    $d->{expire} = '0',
>  	    $d->{role} = 'root';
>  	} else {
> -	    next if $userid !~ m/^(?<username>.+)\@pmg$/;
> -	    $line = "$+{username}:";
> +	    next if $userid !~ m/^(?<username>.+)\@(${realm_regex})$/;
> +	    $line = "$d->{userid}:";
>  	}
>  
>  	for my $k (qw(enable expire crypt_pass role email firstname lastname keys)) {
> diff --git a/src/PMG/Utils.pm b/src/PMG/Utils.pm
> index 0b8945f..04a3a2e 100644
> --- a/src/PMG/Utils.pm
> +++ b/src/PMG/Utils.pm
> @@ -49,12 +49,30 @@ postgres_admin_cmd
>  try_decode_utf8
>  );
>  
> -my $valid_pmg_realms = ['pam', 'pmg', 'quarantine'];
> +my $user_regex = qr![^\s:/]+!;
> +
> +sub valid_pmg_realm_regex {
> +    my $cfg = PVE::INotify::read_file('realms.cfg');
here

> +    my $ids = $cfg->{ids};
> +    my $realms = ['pam', 'quarantine', sort keys $cfg->{ids}->%* ];
> +    return join('|', @$realms);
> +}
> +
> +sub is_valid_realm {
> +    my ($realm) = @_;
> +    return 0 if !$realm;
> +    return 1 if $realm eq 'pam' || $realm eq 'quarantine'; # built-in ones
> +
> +    my $cfg = PVE::INotify::read_file('realms.cfg');
here

> +    return exists($cfg->{ids}->{$realm}) ? 1 : 0;
> +}
> +
> +PVE::JSONSchema::register_format('pmg-realm', \&is_valid_realm);
>  
>  PVE::JSONSchema::register_standard_option('realm', {
>      description => "Authentication domain ID",
>      type => 'string',
> -    enum => $valid_pmg_realms,
> +    format => 'pmg-realm',
>      maxLength => 32,
>  });
>  
> @@ -82,16 +100,15 @@ sub verify_username {
>  	die "user name '$username' is too short\n" if !$noerr;
>  	return undef;
>      }
> -    if ($len > 64) {
> -	die "user name '$username' is too long ($len > 64)\n" if !$noerr;
> +    if ($len > 128) {
> +	die "user name '$username' is too long ($len > 128)\n" if !$noerr;
>  	return undef;
>      }
>  
>      # we only allow a limited set of characters. Colons aren't allowed, because we store usernames
>      # with colon separated lists! slashes aren't allowed because it is used as pve API delimiter
>      # also see "man useradd"
> -    my $realm_list = join('|', @$valid_pmg_realms);
> -    if ($username =~ m!^([^\s:/]+)\@(${realm_list})$!) {
> +    if ($username =~ m!^(${user_regex})\@([A-Za-z][A-Za-z0-9\.\-_]+)$!) {
>  	return wantarray ? ($username, $1, $2) : $username;
>      }
>  



_______________________________________________
pmg-devel mailing list
pmg-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pmg-devel


  reply	other threads:[~2025-02-26 14:40 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-26 14:07 [pmg-devel] [PATCH perl-rs/pmg-api/widget-toolkit/pmg-gui v8 0/13] fix #3892: OpenID Connect Markus Frank
2025-02-26 14:07 ` [pmg-devel] [PATCH proxmox-perl-rs v8 1/13] move openid code from pve-rs to common Markus Frank
2025-02-26 16:57   ` [pmg-devel] applied: " Thomas Lamprecht
2025-02-26 14:07 ` [pmg-devel] [PATCH proxmox-perl-rs v8 2/13] remove empty PMG::RS::OpenId package to avoid confusion Markus Frank
2025-02-26 16:58   ` [pmg-devel] applied: " Thomas Lamprecht
2025-02-26 17:55   ` [pmg-devel] " Stoiko Ivanov
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-api v8 3/13] config: add plugin system for authentication realms Markus Frank
2025-02-26 14:40   ` Stoiko Ivanov [this message]
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-api v8 4/13] config: add oidc type authentication realm Markus Frank
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-api v8 5/13] api: add/update/remove authentication realms like in PVE Markus Frank
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-api v8 6/13] api: oidc login similar to PVE Markus Frank
2025-02-26 14:41   ` Stoiko Ivanov
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-api v8 7/13] api: users: create user with a specified realm Markus Frank
2025-02-26 15:36   ` Mira Limbeck
2025-02-26 16:29     ` Mira Limbeck
2025-02-26 14:07 ` [pmg-devel] [PATCH widget-toolkit v8 08/13] fix: window: AuthEditBase: rename variable 'realm' to 'type' Markus Frank
2025-02-26 17:52   ` [pmg-devel] partially-applied-series: " Thomas Lamprecht
2025-02-26 14:07 ` [pmg-devel] [PATCH widget-toolkit v8 09/13] panel: AuthView: change API path in pmx-domains model Markus Frank
2025-02-26 14:07 ` [pmg-devel] [PATCH widget-toolkit v8 10/13] form: RealmComboBox: add option to change the API path Markus Frank
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-gui v8 11/13] login: add option to login with OIDC realm Markus Frank
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-gui v8 12/13] add realms panel to user management Markus Frank
2025-02-26 14:07 ` [pmg-devel] [PATCH pmg-gui v8 13/13] user: add realm field for user creation Markus Frank
2025-02-26 20:17 ` [pmg-devel] applied: [PATCH perl-rs/pmg-api/widget-toolkit/pmg-gui v8 0/13] fix #3892: OpenID Connect Thomas Lamprecht

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250226154015.1004f27c@rosa.proxmox.com \
    --to=s.ivanov@proxmox.com \
    --cc=m.frank@proxmox.com \
    --cc=pmg-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal