@@ -42,6 +42,7 @@ import bootstrap.liftweb.AuthenticationMethods
4242import bootstrap .liftweb .RudderConfig
4343import bootstrap .liftweb .RudderInMemoryUserDetailsService
4444import bootstrap .liftweb .RudderProperties
45+ import cats .syntax .apply .*
4546import com .github .benmanes .caffeine .cache .Caffeine
4647import com .normation .errors .IOResult
4748import com .normation .plugins .RudderPluginModule
@@ -72,6 +73,7 @@ import org.springframework.context.annotation.Bean
7273import org .springframework .context .annotation .Configuration
7374import org .springframework .core .ParameterizedTypeReference
7475import org .springframework .core .convert .converter .Converter
76+ import org .springframework .http .MediaType
7577import org .springframework .http .RequestEntity
7678import org .springframework .http .ResponseEntity
7779import org .springframework .security .authentication .AbstractAuthenticationToken
@@ -1050,7 +1052,7 @@ class RudderOidcUserService(
10501052 roleApiMapping : RoleApiMapping
10511053) extends OidcUserService with RudderUserServerMapping [OidcUserRequest , OidcUser , RudderUserDetail & OidcUser ] {
10521054 // we need to use our copy of DefaultOAuth2UserService to log/manage errors
1053- super .setOauth2UserService(new RudderDefaultOAuth2UserService ()): @ unchecked
1055+ super .setOauth2UserService(new RudderDefaultOAuth2UserService (registrationRepository )): @ unchecked
10541056
10551057 override val protocolId = RudderOidcUserService .PROTOCOL_ID
10561058 override val protocolName = " OIDC"
@@ -1077,7 +1079,7 @@ class RudderOAuth2UserService(
10771079 roleApiMapping : RoleApiMapping
10781080) extends OAuth2UserService [OAuth2UserRequest , OAuth2User ]
10791081 with RudderUserServerMapping [OAuth2UserRequest , OAuth2User , RudderUserDetail & OAuth2User ] {
1080- val defaultUserService = new RudderDefaultOAuth2UserService ()
1082+ val defaultUserService = new RudderDefaultOAuth2UserService (registrationRepository )
10811083
10821084 override val protocolId = RudderOAuth2UserService .PROTOCOL_ID
10831085 override val protocolName = " OAuth2"
@@ -1130,7 +1132,8 @@ object BuildLogout {
11301132 }
11311133}
11321134
1133- class RudderDefaultOAuth2UserService extends DefaultOAuth2UserService with DebugOAuth2Attributes {
1135+ class RudderDefaultOAuth2UserService (registrationRepository : RudderClientRegistrationRepository )
1136+ extends DefaultOAuth2UserService with DebugOAuth2Attributes {
11341137
11351138 /*
11361139 * this is a copy of parent method with more logs/error management
@@ -1147,7 +1150,9 @@ class RudderDefaultOAuth2UserService extends DefaultOAuth2UserService with Debug
11471150 private val requestEntityConverter : Converter [OAuth2UserRequest , RequestEntity [? ]] = new OAuth2UserRequestEntityConverter
11481151
11491152 private val restOperations : RestOperations = {
1150- val restTemplate = new RestTemplate
1153+ val restTemplate = new RestTemplate ()
1154+ // TODO: set message converters instead of intercepting exception ? https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/message-converters.html
1155+ // restTemplate.setMessageConverters(List(JwtConverter(registration.getProviderDetails.getJwkSetUri)).asJava)
11511156 restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler )
11521157 restTemplate
11531158 }
@@ -1205,8 +1210,11 @@ class RudderDefaultOAuth2UserService extends DefaultOAuth2UserService with Debug
12051210 userRequest : OAuth2UserRequest ,
12061211 request : RequestEntity [? ]
12071212 ): ResponseEntity [java.util.Map [String , AnyRef ]] = {
1213+ given jwtConfig : Option [JwtConfig ] =
1214+ registrationRepository.registrations.get(userRequest.getClientRegistration.getRegistrationId).map(_.jwtConfig)
1215+ given ClientRegistration = userRequest.getClientRegistration
12081216 try {
1209- return this .restOperations.exchange(request, PARAMETERIZED_RESPONSE_TYPE )
1217+ this .restOperations.exchange(request, PARAMETERIZED_RESPONSE_TYPE )
12101218 } catch {
12111219 case ex : OAuth2AuthorizationException =>
12121220 var oauth2Error : OAuth2Error = ex.getError
@@ -1226,6 +1234,8 @@ class RudderDefaultOAuth2UserService extends DefaultOAuth2UserService with Debug
12261234 null
12271235 )
12281236 throw new OAuth2AuthenticationException (oauth2Error, oauth2Error.toString, ex)
1237+ case JwtContentTypeException (jwtResponse) =>
1238+ jwtResponse
12291239 case ex : UnknownContentTypeException =>
12301240 val errorMessage : String =
12311241 " An error occurred while attempting to retrieve the UserInfo Resource from '" + userRequest.getClientRegistration.getProviderDetails.getUserInfoEndpoint.getUri + " ': response contains invalid content type '" + ex.getContentType.toString + " '. " + " The UserInfo Response should return a JSON object (content type 'application/json') " + " that contains a collection of name and value pairs of the claims about the authenticated End-User. " + " Please ensure the UserInfo Uri in UserInfoEndpoint for Client Registration '" + userRequest.getClientRegistration.getRegistrationId + " ' conforms to the UserInfo Endpoint, " + " as defined in OpenID Connect 1.0: 'https://openid.net/specs/openid-connect-core-1_0.html#UserInfo'"
@@ -1565,3 +1575,23 @@ trait DebugOAuth2TokenAttributes extends DebugOAuth2Attributes {
15651575 def pivotAttributeRegistration : RegistrationWithPivotAttribute
15661576 def pivotAttribute : String = pivotAttributeRegistration.pivotAttributeName
15671577}
1578+
1579+ // Special case for JWT interception upon content-type exception in getResponse : signed/encrypted response.
1580+ // Spring has no direct support for these, we need to use Nimbus for decrypting
1581+ object JwtContentTypeException {
1582+
1583+ private val APPLICATION_JWT : MediaType = new MediaType (" application" , " jwt" )
1584+
1585+ def unapply (
1586+ ex : UnknownContentTypeException
1587+ )(using
1588+ jwtConfig : Option [JwtConfig ],
1589+ clientRegistration : ClientRegistration
1590+ ): Option [ResponseEntity [util.Map [String , AnyRef ]]] = {
1591+ given JwtConfig = jwtConfig.getOrElse(JwtConfig .default(clientRegistration.getProviderDetails.getJwkSetUri))
1592+ Option (ex.getResponseHeaders).filter(_ => ex.getContentType.isCompatibleWith(APPLICATION_JWT )) *>
1593+ decodeJwt(ex.getResponseBodyAsString).toOption
1594+ .map(jwt => ResponseEntity .ok().body(jwt.getClaims))
1595+ }
1596+
1597+ }
0 commit comments