From fa2301ab0eb55e161205133295eff455fca7587f Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Fri, 6 Nov 2020 17:10:10 +0100 Subject: [PATCH] Force OIDC claim types according to config (#2330) --- .../Lemonldap/NG/Portal/Lib/OpenIDConnect.pm | 119 +++++++++++++++--- 1 file changed, 103 insertions(+), 16 deletions(-) diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm index 16ad1b1d1..65208ea97 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm @@ -1349,9 +1349,13 @@ sub buildUserInfoResponse { my $list = $self->getAttributesListFromClaim( $rp, $claim ); next unless $list; foreach my $attribute (@$list) { - my $session_key = - $self->conf->{oidcRPMetaDataExportedVars}->{$rp}->{$attribute}; + my @attrConf = split /;/, + ( $self->conf->{oidcRPMetaDataExportedVars}->{$rp}->{$attribute} + || "" ); + my $session_key = $attrConf[0]; if ($session_key) { + my $type = $attrConf[1] || 'string'; + my $array = $attrConf[2] || 'auto'; my $session_value; @@ -1366,21 +1370,25 @@ sub buildUserInfoResponse { $session_value = $session->data->{$session_key}; } - # Convert mutli-valued attributes to arrays - my $separator = $self->conf->{multiValuesSeparator}; - if ( $session_value and $session_value =~ /$separator/ ) { - my @session_array = - split( $separator, $session_value ); - $session_value = \@session_array; - } + # Handle empty values, arrays, type, etc. + $session_value = + $self->_formatValue( $session_value, $type, $array, + $attribute, $req->user ); - # Address is a JSON object - if ( $claim eq "address" ) { - $userinfo_response->{address}->{$attribute} = - $session_value; - } - else { - $userinfo_response->{$attribute} = $session_value; + # From this point on, do NOT touch $session_value or you will break + # the variable's type. + + # Only release claim if it has a value + if ( defined $session_value ) { + + # Address is a JSON object + if ( $claim eq "address" ) { + $userinfo_response->{address}->{$attribute} = + $session_value; + } + else { + $userinfo_response->{$attribute} = $session_value; + } } } } @@ -1389,6 +1397,85 @@ sub buildUserInfoResponse { return $userinfo_response; } +sub _formatValue { + my ( $self, $session_value, $type, $array, $attribute, $user ) = @_; + + # If $session_value is not a scalar, return it as is + unless ( ref($session_value) ) { + if ( defined $session_value ) { + + # Empty strings or lists are invalid values + if ( length($session_value) > 0 ) { + + # Format value for JSON output: multi valuation, JSON type... + my $separator = $self->conf->{multiValuesSeparator}; + return $self->_applyType( $session_value, $separator, $type, + $array, $attribute, $user ); + } + else { + return undef; + } + } + } + return $session_value; +} + +sub _applyType { + my ( $self, $session_value, $separator, $type, $array, $attribute, $user ) + = @_; + + # Array style handling + # In auto array mode, split as array only if there are multiple values + if ( $array eq "auto" ) { + if ( $session_value and $session_value =~ /$separator/ ) { + $session_value = [ map { $self->_forceType( $_, $type ) } + split( $separator, $session_value ) ]; + } + else { + $session_value = $self->_forceType( $session_value, $type ); + } + + # In always array mode, always split (even on empty values) + } + elsif ( $array eq "always" ) { + $session_value = [ map { $self->_forceType( $_, $type ) } + split( $separator, $session_value ) ]; + } + + # In never array mode, return the string as-is + else { + # No type coaxing is possible on a flattened string + if ( $session_value =~ /$separator/ and $type ne "string" ) { + $self->logger->warn( "Cannot force type of value $session_value" + . " for attribute $attribute of user " + . $user + . " because it is multi-valued. " + . "Use auto or always as array type for this attribute" ); + } + else { + $session_value = $self->_forceType( $session_value, $type ); + } + } + + return $session_value; +} + +sub _forceType { + my ( $self, $val, $type ) = @_; + if ( $type eq "bool" ) { + return ( $val ? JSON::true : JSON::false ); + } + + if ( $type eq "int" ) { + + # Coax into int + return ( $val + 0 ); + } + + # Coax into string + return ( $val . "" ); +} + # Return JWT # @param payload JWT content # @param alg Signature algorithm