@@ -575,23 +575,6 @@ public String main(@RequestParam("code") String code,
575575 return "error" ;
576576 }
577577 }
578- String apiStandard = SessionData .getApiStandard (session );
579- if (apiStandard .equalsIgnoreCase ("BerlinGroup" )){ // fetch Consent information
580- HttpHeaders headers = new HttpHeaders ();
581- headers .setBearerAuth (SessionData .getAccessToken (session ));
582- HttpEntity <String > entity = new HttpEntity <>(headers );
583- String consentId = SessionData .getConsentId (session );
584- ResponseEntity <Map > response = restTemplate .exchange (getConsentInformationBerlinGroup .replace ("CONSENT_ID" , consentId ), HttpMethod .GET , entity , Map .class );
585- int frequencyPerDay = (int )response .getBody ().get ("frequencyPerDay" );
586- String consentStatus = (String )response .getBody ().get ("consentStatus" );
587- String validUntil = (String )response .getBody ().get ("validUntil" );
588- boolean recurringIndicator = (boolean )response .getBody ().get ("recurringIndicator" );
589- session .setAttribute ("frequencyPerDay" , String .valueOf (frequencyPerDay ));
590- session .setAttribute ("consentStatus" , consentStatus );
591- session .setAttribute ("validUntil" , validUntil );
592- session .setAttribute ("recurringIndicator" , String .valueOf (recurringIndicator ));
593- }
594-
595578 return "redirect:/main" ;
596579 }
597580
@@ -637,6 +620,26 @@ public String main(HttpSession session, Model model) {
637620 model .addAttribute ("consentId" , consentId );
638621 String consentRequestId = SessionData .getConsentRequestId (session );
639622 model .addAttribute ("consentRequestId" , consentRequestId );
623+ // Berlin Group has no OAuth token to hang this off (see requestConsentsBerlinGroup), so unlike
624+ // the other flows this can't be captured once at OIDC-callback time — fetch it fresh here,
625+ // authenticating with the Consent-ID header the same way getAccountsBerlinGroup does.
626+ if ("BerlinGroup" .equalsIgnoreCase (apiStandard ) && StringUtils .isNotBlank (consentId )) {
627+ try {
628+ HttpHeaders consentInfoHeaders = new HttpHeaders ();
629+ consentInfoHeaders .add ("Consent-ID" , consentId );
630+ HttpEntity <String > consentInfoEntity = new HttpEntity <>(consentInfoHeaders );
631+ ResponseEntity <Map > consentInfoResponse = restTemplate .exchange (
632+ getConsentInformationBerlinGroup .replace ("CONSENT_ID" , consentId ),
633+ HttpMethod .GET , consentInfoEntity , Map .class );
634+ Map consentInfoBody = consentInfoResponse .getBody ();
635+ session .setAttribute ("frequencyPerDay" , String .valueOf (consentInfoBody .get ("frequencyPerDay" )));
636+ session .setAttribute ("consentStatus" , String .valueOf (consentInfoBody .get ("consentStatus" )));
637+ session .setAttribute ("validUntil" , String .valueOf (consentInfoBody .get ("validUntil" )));
638+ session .setAttribute ("recurringIndicator" , String .valueOf (consentInfoBody .get ("recurringIndicator" )));
639+ } catch (RestClientException e ) {
640+ logger .warn ("Could not fetch Berlin Group consent info for consent_id=" + consentId , e );
641+ }
642+ }
640643 String consentStatus = (String )session .getAttribute ("consentStatus" );
641644 model .addAttribute ("consentStatus" , consentStatus );
642645 String frequencyPerDay = (String )session .getAttribute ("frequencyPerDay" );
@@ -668,6 +671,12 @@ public String requestConsentsBerlinGroup(@RequestParam("bank") String bankId,
668671 String clientCredentialsToken = getClientCredentialsToken ();
669672 HttpHeaders headers = new HttpHeaders ();
670673 headers .setBearerAuth (clientCredentialsToken );
674+ // OBP-API stores these into the consent's JWT (ConsentUtil.createObpConsentBerlinGroup)
675+ // and Portal's SCA confirmation flow reads TPP-Redirect-URI back out of it to send the
676+ // browser here once the consent is ACCEPTED, so main() (params="!code") can render it
677+ // straight out of session — no OAuth code/token round-trip needed for Berlin Group.
678+ headers .add ("TPP-Redirect-URI" , redirectUri );
679+ headers .add ("TPP-Nok-Redirect-URI" , redirectUri );
671680 String recurringIndicator = recurring_indicator ;
672681 String expirationDateTime = convertTimeFormat (expiration_time );
673682 // Berlin Group's `validUntil` is validated server-side as a plain yyyy-MM-dd date,
@@ -686,15 +695,28 @@ public String requestConsentsBerlinGroup(@RequestParam("bank") String bankId,
686695 Integer .parseInt (frequencyPerDay ),
687696 false
688697 );
689- String consentId = "" ;
698+ String consentId ;
699+ String scaRedirectUrl ;
690700 try {
691701 HttpEntity <PostConsentJson > request = new HttpEntity <>(body , headers );
692702 Map response = restTemplate .postForObject (createBerlinGroupConsentsUrl , request , Map .class );
693703 consentId = ((Map <String , String >) response ).get ("consentId" );
694- // main() reads this back via SessionData.getConsentId() when it later fetches the
695- // Berlin Group consent info; a raw session.setAttribute("consent_id", ...) here writes
696- // to a different storage slot and leaves that read null.
704+ Map links = (Map ) response .get ("_links" );
705+ Map scaRedirect = links == null ? null : (Map ) links .get ("scaRedirect" );
706+ scaRedirectUrl = scaRedirect == null ? null : (String ) scaRedirect .get ("href" );
707+ if (StringUtils .isBlank (scaRedirectUrl )) {
708+ model .addAttribute ("errorMsg" , "OBP-API did not return a consent._links.scaRedirect URL "
709+ + "to confirm this consent (response: " + response + "). Without it there is no way "
710+ + "to complete Berlin Group's SCA step." );
711+ return "error" ;
712+ }
713+ // main() (params="!code") reads all of this straight out of session when the browser
714+ // lands back on redirectUri after SCA confirmation on Portal — Berlin Group's consent
715+ // is authorized via Consent-ID, not an OAuth token, so there's no code/id_token exchange
716+ // to do here the way the other flows need.
697717 SessionData .setConsentId (session , consentId );
718+ SessionData .setApiStandard (session , "BerlinGroup" );
719+ SessionData .setBankId (session , bankId );
698720 } catch (HttpClientErrorException e ) {
699721 String error = "Sorry! Cannot create the consent." ;
700722 logger .error (error , e );
@@ -706,58 +728,10 @@ public String requestConsentsBerlinGroup(@RequestParam("bank") String bankId,
706728 return "index_bg" ;
707729 }
708730
709-
710- //{"client_id", "bank_id", "consent_id", "response_type=code", "scope", "redirect_uri", "state"})
711- Map <String , String > queryParam = new LinkedHashMap <>();
712- queryParam .put ("client_id" , clientId );
713- queryParam .put ("response_type" , "code+id_token" );
714- // include OBP scopes, add OAuth2 and OIDC related scope: "openid" and "offline"
715- consents = ArrayUtils .addAll (new String []{"openid" , "offline" }, consents );
716- String scope = Stream .of (consents )
717- .distinct ()
718- .map (this ::encodeQueryParam )
719- .collect (Collectors .joining ("+" ));
720-
721- queryParam .put ("scope" , scope );
722- String encodeRedirectUri = URLEncoder .encode (redirectUri , "UTF-8" );
723- queryParam .put ("redirect_uri" , encodeRedirectUri );
724- final String state = UUID .randomUUID ().toString ();
725- final String nonce = UUID .randomUUID ().toString ();
726- queryParam .put ("state" , state );
727- queryParam .put ("nonce" , nonce );
728- SessionData .setState (session , state );
729- SessionData .setNonce (session , nonce );
730-
731- // the parameter consent_id and bank_id are mandatory, these two parameter is not standard parameter of OAuth2 and OIDC
732- queryParam .put ("consent_id" , consentId );
733- queryParam .put ("bank_id" , bankId );
734- String ibansTrimmed = Arrays .asList (ibans ).stream ()
735- .map (n -> String .valueOf (n ))
736- .collect (Collectors .joining ("," ));
737- queryParam .put ("iban" , ibansTrimmed );
738- queryParam .put ("recurring_indicator" , recurring_indicator );
739- queryParam .put ("frequency_per_day" , frequency_per_day );
740- queryParam .put ("expiration_time" , expirationDateTime );
741- queryParam .put ("api_standard" , "BerlinGroup" );
742- SessionData .setApiStandard (session , "BerlinGroup" );
743- // TODO the acr_values is just temp example value, can be space split values, need check and supply real values.
744- //queryParam.put("acr_values", "urn:openbankproject:psd2:sca");
745-
746- // add code_challenge
747- final String codeVerifier = PKCEUtil .generateCodeVerifier ();
748- SessionData .setCodeVerifier (session , codeVerifier );
749- final String codeChallenge = PKCEUtil .generateCodeChallenge (codeVerifier );
750- queryParam .put ("code_challenge_method" , "S256" );
751- queryParam .put ("code_challenge" , codeChallenge );
752-
753- String queryParamStr = queryParam .entrySet ().stream ().map (it -> it .getKey () + "=" + it .getValue ()).collect (Collectors .joining ("&" ));
754- String authorizationEndpoint = openIDConfiguration .getAuthorizationEndpoint ();
755- String redirectUrl = "redirect:" + authorizationEndpoint + "?" + queryParamStr ;
756-
757731 // if current user is authenticated, remove user info from session, to do re-authentication
758732 SessionData .remoteUserInfo (session );
759733
760- return redirectUrl ;
734+ return "redirect:" + scaRedirectUrl ;
761735 } catch (HttpStatusCodeException httpException ) {
762736 logger .error ("Error: " , httpException );
763737 String errorDetail = httpException .getStatusCode () + " " + httpException .getStatusText ();
0 commit comments