2929import org .cloudfoundry .identity .uaa .util .JsonUtils ;
3030import org .cloudfoundry .identity .uaa .util .ObjectUtils ;
3131import org .cloudfoundry .identity .uaa .util .UaaHttpRequestUtils ;
32+ import org .cloudfoundry .identity .uaa .util .UaaUrlUtils ;
3233import org .cloudfoundry .identity .uaa .zone .BrandingInformation ;
3334import org .cloudfoundry .identity .uaa .zone .beans .IdentityZoneManager ;
3435import org .springframework .beans .factory .annotation .Qualifier ;
7475
7576@ Slf4j
7677@ Controller
77- @ RequestMapping ("/invitations" )
78+ @ RequestMapping (value = { "/invitations" , "/z/{subdomain}/invitations" } )
7879public class InvitationsController {
7980
8081 private static final String EMAIL = "email" ;
@@ -119,6 +120,7 @@ public String acceptInvitePage(@RequestParam String code, Model model, HttpServl
119120
120121 ExpiringCode expiringCode = expiringCodeStore .peekCode (code , identityZoneManager .getCurrentIdentityZoneId ());
121122 if ((null == expiringCode ) || (null != expiringCode .getIntent () && !INVITATION .name ().equals (expiringCode .getIntent ()))) {
123+ model .addAttribute ("pathPrefix" , UaaUrlUtils .getZonePathPrefix (request ));
122124 return handleUnprocessableEntity (model , response , "error_message_code" , "code_expired" , "invitations/accept_invite" );
123125 }
124126
@@ -161,6 +163,7 @@ public String acceptInvitePage(@RequestParam String code, Model model, HttpServl
161163 AnonymousAuthenticationToken token = new AnonymousAuthenticationToken ("scim.invite" , uaaPrincipal ,
162164 Collections .singletonList (UaaAuthority .UAA_INVITED ));
163165 SecurityContextHolder .getContext ().setAuthentication (token );
166+ model .addAttribute ("pathPrefix" , UaaUrlUtils .getZonePathPrefix (request ));
164167 model .addAttribute ("provider" , provider .getType ());
165168 model .addAttribute ("code" , code );
166169 model .addAttribute (EMAIL , codeData .get (EMAIL ));
@@ -170,6 +173,7 @@ public String acceptInvitePage(@RequestParam String code, Model model, HttpServl
170173 return "invitations/accept_invite" ;
171174 } catch (EmptyResultDataAccessException noProviderFound ) {
172175 log .debug ("No available invitation providers for email:%s, id:%s" .formatted (codeData .get (EMAIL ), codeData .get ("user_id" )));
176+ model .addAttribute ("pathPrefix" , UaaUrlUtils .getZonePathPrefix (request ));
173177 return handleUnprocessableEntity (model , response , "error_message_code" , "no_suitable_idp" , "invitations/accept_invite" );
174178 }
175179 }
@@ -244,8 +248,11 @@ public String acceptInvitation(@RequestParam("password") String password,
244248 @ RequestParam ("code" ) String code ,
245249 @ RequestParam (value = "does_user_consent" , required = false ) boolean doesUserConsent ,
246250 Model model ,
251+ HttpServletRequest request ,
247252 HttpServletResponse response ) {
248253
254+ String pathPrefix = UaaUrlUtils .getZonePathPrefix (request );
255+
249256 PasswordConfirmationValidation validation = new PasswordConfirmationValidation (password , passwordConfirmation );
250257
251258 UaaPrincipal principal = (UaaPrincipal ) SecurityContextHolder .getContext ().getAuthentication ().getPrincipal ();
@@ -255,43 +262,46 @@ public String acceptInvitation(@RequestParam("password") String password,
255262 if (expiringCode == null || expiringCode .getData () == null ) {
256263 log .debug ("Failing invitation. Code not found." );
257264 SecurityContextHolder .clearContext ();
265+ model .addAttribute ("pathPrefix" , pathPrefix );
258266 return handleUnprocessableEntity (model , response , "error_message_code" , "code_expired" , "invitations/accept_invite" );
259267 }
260268 Map <String , String > data = JsonUtils .readValue (expiringCode .getData (), new TypeReference <>() {
261269 });
262270 if (principal == null || data .get ("user_id" ) == null || !data .get ("user_id" ).equals (principal .getId ())) {
263271 log .debug ("Failing invitation. Code and user ID mismatch." );
264272 SecurityContextHolder .clearContext ();
273+ model .addAttribute ("pathPrefix" , pathPrefix );
265274 return handleUnprocessableEntity (model , response , "error_message_code" , "code_expired" , "invitations/accept_invite" );
266275 }
267276
268277 final String newCode = expiringCodeStore .generateCode (expiringCode .getData (), new Timestamp (System .currentTimeMillis () + (10 * 60 * 1000 )), expiringCode .getIntent (), identityZoneManager .getCurrentIdentityZoneId ()).getCode ();
269278 BrandingInformation zoneBranding = identityZoneManager .getCurrentIdentityZone ().getConfig ().getBranding ();
270279 if (zoneBranding != null && zoneBranding .getConsent () != null && !doesUserConsent ) {
271- return processErrorReload (newCode , model , response , "error_message_code" , "missing_consent" );
280+ return processErrorReload (newCode , model , response , "error_message_code" , "missing_consent" , pathPrefix );
272281 }
273282 if (!validation .valid ()) {
274- return processErrorReload (newCode , model , response , "error_message_code" , validation .getMessageCode ());
283+ return processErrorReload (newCode , model , response , "error_message_code" , validation .getMessageCode (), pathPrefix );
275284 }
276285 try {
277286 passwordValidator .validate (password );
278287 } catch (InvalidPasswordException e ) {
279- return processErrorReload (newCode , model , response , "error_message" , e .getMessagesAsOneString ());
288+ return processErrorReload (newCode , model , response , "error_message" , e .getMessagesAsOneString (), pathPrefix );
280289 }
281290 AcceptedInvitation invitation ;
282291 try {
283292 invitation = invitationsService .acceptInvitation (newCode , password );
284293 } catch (HttpClientErrorException e ) {
294+ model .addAttribute ("pathPrefix" , pathPrefix );
285295 return handleUnprocessableEntity (model , response , "error_message_code" , "code_expired" , "invitations/accept_invite" );
286296 }
287- String res = "redirect: /login?success=invite_accepted" ;
297+ String res = pathPrefix + " /login?success=invite_accepted" ;
288298 if (!invitation .getRedirectUri ().equals ("/home" )) {
289299 res += "&" + FORM_REDIRECT_PARAMETER + "=" + invitation .getRedirectUri ();
290300 }
291- return res ;
301+ return "redirect:" + res ;
292302 }
293303
294- private String processErrorReload (String code , Model model , HttpServletResponse response , String errorCode , String error ) {
304+ private String processErrorReload (String code , Model model , HttpServletResponse response , String errorCode , String error , String pathPrefix ) {
295305 ExpiringCode expiringCode = expiringCodeStore .retrieveCode (code , identityZoneManager .getCurrentIdentityZoneId ());
296306 Map <String , String > codeData = JsonUtils .readValue (expiringCode .getData (), new TypeReference <>() {
297307 });
@@ -300,9 +310,11 @@ private String processErrorReload(String code, Model model, HttpServletResponse
300310
301311 model .addAttribute (errorCode , error );
302312 model .addAttribute ("code" , newCode );
303- return "redirect:accept" ;
313+ String redirectTarget = (pathPrefix != null && !pathPrefix .isEmpty ()) ? pathPrefix + "/invitations/accept" : "accept" ;
314+ return "redirect:" + redirectTarget ;
304315 } catch (EmptyResultDataAccessException noProviderFound ) {
305316 log .debug ("No available invitation providers for email:%s, id:%s" .formatted (codeData .get (EMAIL ), codeData .get ("user_id" )));
317+ model .addAttribute ("pathPrefix" , pathPrefix );
306318 return handleUnprocessableEntity (model , response , "error_message_code" , "no_suitable_idp" , "invitations/accept_invite" );
307319 }
308320 }
@@ -312,7 +324,8 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u
312324 @ RequestParam ("enterprise_password" ) String password ,
313325 @ RequestParam ("enterprise_email" ) String email ,
314326 @ RequestParam String code ,
315- Model model , HttpServletResponse response ) {
327+ Model model , HttpServletRequest request , HttpServletResponse response ) {
328+ String pathPrefix = UaaUrlUtils .getZonePathPrefix (request );
316329
317330 ExpiringCode expiringCode = expiringCodeStore .retrieveCode (code , identityZoneManager .getCurrentIdentityZoneId ());
318331 if (expiringCode == null ) {
@@ -330,9 +343,11 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u
330343 authenticationManager = zoneAwareAuthenticationManager .getLdapAuthenticationManager (identityZoneManager .getCurrentIdentityZone (), ldapProvider ).getLdapManagerActual ();
331344 } catch (EmptyResultDataAccessException e ) {
332345 //ldap provider was not available
346+ model .addAttribute ("pathPrefix" , pathPrefix );
333347 return handleUnprocessableEntity (model , response , "error_message_code" , "no_suitable_idp" , "invitations/accept_invite" );
334348 } catch (Exception x ) {
335349 log .error ("Unable to retrieve LDAP config." , x );
350+ model .addAttribute ("pathPrefix" , pathPrefix );
336351 return handleUnprocessableEntity (model , response , "error_message_code" , "no_suitable_idp" , "invitations/accept_invite" );
337352 }
338353 Authentication authentication ;
@@ -345,6 +360,7 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u
345360 model .addAttribute (EMAIL , data .get (EMAIL ));
346361 model .addAttribute ("provider" , OriginKeys .LDAP );
347362 model .addAttribute ("code" , expiringCodeStore .generateCode (expiringCode .getData (), new Timestamp (System .currentTimeMillis () + (10 * 60 * 1000 )), null , identityZoneManager .getCurrentIdentityZoneId ()).getCode ());
363+ model .addAttribute ("pathPrefix" , pathPrefix );
348364 return handleUnprocessableEntity (model , response , "error_message" , "invite.email_mismatch" , "invitations/accept_invite" );
349365 }
350366
@@ -354,16 +370,19 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u
354370 userProvisioning .update (user .getId (), user , identityZoneManager .getCurrentIdentityZoneId ());
355371 zoneAwareAuthenticationManager .getLdapAuthenticationManager (identityZoneManager .getCurrentIdentityZone (), ldapProvider ).authenticate (token );
356372 AcceptedInvitation accept = invitationsService .acceptInvitation (newCode , "" );
357- return "redirect:" + "/login?success=invite_accepted&form_redirect_uri=" + URLEncoder .encode (accept .getRedirectUri (), StandardCharsets .UTF_8 );
373+ return "redirect:" + pathPrefix + "/login?success=invite_accepted&form_redirect_uri=" + URLEncoder .encode (accept .getRedirectUri (), StandardCharsets .UTF_8 );
358374 } else {
375+ model .addAttribute ("pathPrefix" , pathPrefix );
359376 return handleUnprocessableEntity (model , response , "error_message" , "not authenticated" , "invitations/accept_invite" );
360377 }
361378 } catch (AuthenticationException x ) {
379+ model .addAttribute ("pathPrefix" , pathPrefix );
362380 return handleUnprocessableEntity (model , response , "error_message" , x .getMessage (), "invitations/accept_invite" );
363381 } catch (Exception x ) {
364382 log .error ("Unable to authenticate against LDAP" , x );
365383 model .addAttribute ("ldap" , true );
366384 model .addAttribute (EMAIL , email );
385+ model .addAttribute ("pathPrefix" , pathPrefix );
367386 return handleUnprocessableEntity (model , response , "error_message" , "bad_credentials" , "invitations/accept_invite" );
368387 }
369388 }
0 commit comments