1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.logic;
20
21 import com.nimbusds.jwt.JWTClaimsSet;
22 import com.nimbusds.jwt.SignedJWT;
23 import java.io.OutputStream;
24 import java.io.OutputStreamWriter;
25 import java.lang.reflect.Method;
26 import java.text.ParseException;
27 import java.time.OffsetDateTime;
28 import java.util.Base64;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.concurrent.ConcurrentHashMap;
35 import org.apache.commons.lang3.StringUtils;
36 import org.apache.commons.lang3.tuple.Pair;
37 import org.apache.syncope.common.lib.Attr;
38 import org.apache.syncope.common.lib.SyncopeClientException;
39 import org.apache.syncope.common.lib.saml2.SAML2LoginResponse;
40 import org.apache.syncope.common.lib.saml2.SAML2Request;
41 import org.apache.syncope.common.lib.saml2.SAML2Response;
42 import org.apache.syncope.common.lib.to.EntityTO;
43 import org.apache.syncope.common.lib.to.Item;
44 import org.apache.syncope.common.lib.to.UserTO;
45 import org.apache.syncope.common.lib.types.CipherAlgorithm;
46 import org.apache.syncope.common.lib.types.ClientExceptionType;
47 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
48 import org.apache.syncope.common.lib.types.SAML2BindingType;
49 import org.apache.syncope.core.logic.saml2.NoOpSessionStore;
50 import org.apache.syncope.core.logic.saml2.SAML2ClientCache;
51 import org.apache.syncope.core.logic.saml2.SAML2SP4UIContext;
52 import org.apache.syncope.core.logic.saml2.SAML2SP4UIUserManager;
53 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
54 import org.apache.syncope.core.persistence.api.dao.SAML2SP4UIIdPDAO;
55 import org.apache.syncope.core.persistence.api.entity.Implementation;
56 import org.apache.syncope.core.persistence.api.entity.SAML2SP4UIIdP;
57 import org.apache.syncope.core.provisioning.api.RequestedAuthnContextProvider;
58 import org.apache.syncope.core.provisioning.api.data.AccessTokenDataBinder;
59 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
60 import org.apache.syncope.core.spring.implementation.ImplementationManager;
61 import org.apache.syncope.core.spring.security.AuthContextUtils;
62 import org.apache.syncope.core.spring.security.AuthDataAccessor;
63 import org.apache.syncope.core.spring.security.Encryptor;
64 import org.opensaml.saml.common.xml.SAMLConstants;
65 import org.opensaml.saml.saml2.core.AuthnRequest;
66 import org.opensaml.saml.saml2.core.LogoutResponse;
67 import org.opensaml.saml.saml2.core.NameID;
68 import org.opensaml.saml.saml2.core.RequestedAuthnContext;
69 import org.opensaml.saml.saml2.core.StatusCode;
70 import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
71 import org.opensaml.saml.saml2.metadata.EntityDescriptor;
72 import org.opensaml.saml.saml2.metadata.impl.AssertionConsumerServiceBuilder;
73 import org.pac4j.core.context.WebContext;
74 import org.pac4j.core.context.session.SessionStore;
75 import org.pac4j.core.exception.http.RedirectionAction;
76 import org.pac4j.core.exception.http.WithContentAction;
77 import org.pac4j.core.exception.http.WithLocationAction;
78 import org.pac4j.core.logout.NoLogoutActionBuilder;
79 import org.pac4j.saml.client.SAML2Client;
80 import org.pac4j.saml.config.SAML2Configuration;
81 import org.pac4j.saml.context.SAML2MessageContext;
82 import org.pac4j.saml.credentials.SAML2Credentials;
83 import org.pac4j.saml.credentials.authenticator.SAML2Authenticator;
84 import org.pac4j.saml.metadata.SAML2ServiceProviderMetadataResolver;
85 import org.pac4j.saml.profile.SAML2Profile;
86 import org.pac4j.saml.redirect.SAML2RedirectionActionBuilder;
87 import org.pac4j.saml.sso.impl.SAML2AuthnRequestBuilder;
88 import org.springframework.beans.BeanUtils;
89 import org.springframework.core.io.support.ResourcePatternResolver;
90 import org.springframework.security.access.prepost.PreAuthorize;
91 import org.springframework.util.ResourceUtils;
92
93 public class SAML2SP4UILogic extends AbstractSAML2SP4UILogic {
94
95 protected static final String JWT_CLAIM_IDP_ENTITYID = "IDP_ENTITYID";
96
97 protected static final String JWT_CLAIM_NAMEID_FORMAT = "NAMEID_FORMAT";
98
99 protected static final String JWT_CLAIM_NAMEID_VALUE = "NAMEID_VALUE";
100
101 protected static final String JWT_CLAIM_SESSIONINDEX = "SESSIONINDEX";
102
103 protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
104
105 protected final AccessTokenDataBinder accessTokenDataBinder;
106
107 protected final SAML2ClientCache saml2ClientCacheLogin;
108
109 protected final SAML2ClientCache saml2ClientCacheLogout;
110
111 protected final SAML2SP4UIUserManager userManager;
112
113 protected final SAML2SP4UIIdPDAO idpDAO;
114
115 protected final AuthDataAccessor authDataAccessor;
116
117 protected final Map<String, String> metadataCache = new ConcurrentHashMap<>();
118
119 protected final Map<String, RequestedAuthnContextProvider> perContextRACP = new ConcurrentHashMap<>();
120
121 public SAML2SP4UILogic(
122 final SAML2SP4UIProperties props,
123 final ResourcePatternResolver resourceResolver,
124 final AccessTokenDataBinder accessTokenDataBinder,
125 final SAML2ClientCache saml2ClientCacheLogin,
126 final SAML2ClientCache saml2ClientCacheLogout,
127 final SAML2SP4UIUserManager userManager,
128 final SAML2SP4UIIdPDAO idpDAO,
129 final AuthDataAccessor authDataAccessor) {
130
131 super(props, resourceResolver);
132
133 this.accessTokenDataBinder = accessTokenDataBinder;
134 this.saml2ClientCacheLogin = saml2ClientCacheLogin;
135 this.saml2ClientCacheLogout = saml2ClientCacheLogout;
136 this.userManager = userManager;
137 this.idpDAO = idpDAO;
138 this.authDataAccessor = authDataAccessor;
139 }
140
141 protected static String validateUrl(final String url) {
142 boolean isValid = true;
143 if (url.contains("..")) {
144 isValid = false;
145 }
146 if (isValid) {
147 isValid = ResourceUtils.isUrl(url);
148 }
149
150 if (!isValid) {
151 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
152 sce.getElements().add("Invalid URL: " + url);
153 throw sce;
154 }
155
156 return url;
157 }
158
159 protected static String getCallbackUrl(final String spEntityID, final String urlContext) {
160 return validateUrl(spEntityID + urlContext + "/assertion-consumer");
161 }
162
163 @PreAuthorize("isAuthenticated()")
164 public void getMetadata(final String spEntityID, final String urlContext, final OutputStream os) {
165 String metadata = metadataCache.get(spEntityID + urlContext);
166 if (metadata == null) {
167 SAML2Configuration cfg = newSAML2Configuration();
168 cfg.setServiceProviderEntityId(spEntityID);
169 cfg.setCallbackUrl(getCallbackUrl(spEntityID, urlContext));
170 SAML2ClientCache.getSPMetadataPath(spEntityID).ifPresent(cfg::setServiceProviderMetadataResourceFilepath);
171
172 EntityDescriptor entityDescriptor =
173 (EntityDescriptor) new SAML2ServiceProviderMetadataResolver(cfg).getEntityDescriptorElement();
174
175 AssertionConsumerService postACS = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).
176 getAssertionConsumerServices().get(0);
177
178 AssertionConsumerService redirectACS = new AssertionConsumerServiceBuilder().buildObject();
179 BeanUtils.copyProperties(postACS, redirectACS);
180 postACS.setBinding(SAML2BindingType.REDIRECT.getUri());
181 postACS.setIndex(1);
182 entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).
183 getAssertionConsumerServices().add(redirectACS);
184
185 entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().
186 removeIf(slo -> !SAML2BindingType.POST.getUri().equals(slo.getBinding())
187 && !SAML2BindingType.REDIRECT.getUri().equals(slo.getBinding()));
188 entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().
189 forEach(slo -> slo.setLocation(
190 getCallbackUrl(spEntityID, urlContext).replace("/assertion-consumer", "/logout")));
191
192 try {
193 metadata = cfg.toMetadataGenerator().getMetadata(entityDescriptor);
194 metadataCache.put(spEntityID + urlContext, metadata);
195 } catch (Exception e) {
196 LOG.error("While generating SP metadata", e);
197 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
198 sce.getElements().add(e.getMessage());
199 throw sce;
200 }
201 }
202
203 try (OutputStreamWriter osw = new OutputStreamWriter(os)) {
204 osw.write(metadata);
205 } catch (Exception e) {
206 LOG.error("While getting SP metadata", e);
207 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
208 sce.getElements().add(e.getMessage());
209 throw sce;
210 }
211 }
212
213 protected SAML2Client getSAML2Client(
214 final SAML2ClientCache saml2ClientCache,
215 final SAML2SP4UIIdP idp,
216 final String spEntityID,
217 final String urlContext) {
218
219 return saml2ClientCache.get(idp.getEntityID(), spEntityID).
220 orElseGet(() -> saml2ClientCache.add(
221 idp, newSAML2Configuration(), spEntityID, getCallbackUrl(spEntityID, urlContext)));
222 }
223
224 protected SAML2Client getSAML2Client(
225 final SAML2ClientCache saml2ClientCache,
226 final String idpEntityID,
227 final String spEntityID,
228 final String urlContext) {
229
230 SAML2SP4UIIdP idp = Optional.ofNullable(idpDAO.findByEntityID(idpEntityID)).
231 orElseThrow(() -> new NotFoundException("SAML 2.0 IdP '" + idpEntityID + '\''));
232
233 return getSAML2Client(saml2ClientCache, idp, spEntityID, urlContext);
234 }
235
236 protected static SAML2Request buildRequest(final String idpEntityID, final RedirectionAction action) {
237 SAML2Request requestTO = new SAML2Request();
238 requestTO.setIdpEntityID(idpEntityID);
239 if (action instanceof WithLocationAction) {
240 WithLocationAction withLocationAction = (WithLocationAction) action;
241
242 requestTO.setBindingType(SAML2BindingType.REDIRECT);
243 requestTO.setContent(withLocationAction.getLocation());
244 } else if (action instanceof WithContentAction) {
245 WithContentAction withContentAction = (WithContentAction) action;
246
247 requestTO.setBindingType(SAML2BindingType.POST);
248 requestTO.setContent(Base64.getMimeEncoder().encodeToString(withContentAction.getContent().getBytes()));
249 }
250 return requestTO;
251 }
252
253 protected Optional<RequestedAuthnContextProvider> getRequestedAuthnContextProvider(final SAML2SP4UIIdP idp) {
254 Implementation impl = idp.getRequestedAuthnContextProvider();
255 if (impl != null) {
256 try {
257 return Optional.of(ImplementationManager.build(
258 impl,
259 () -> perContextRACP.get(impl.getKey()),
260 instance -> perContextRACP.put(impl.getKey(), instance)));
261 } catch (Exception e) {
262 LOG.warn("Cannot instantiate '{}', reverting to default behavior", impl, e);
263 }
264 }
265
266 return Optional.empty();
267 }
268
269 @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
270 public SAML2Request createLoginRequest(
271 final String spEntityID,
272 final String urlContext,
273 final String idpEntityID) {
274
275
276 SAML2SP4UIIdP idp = Optional.ofNullable(idpDAO.findByEntityID(idpEntityID)).
277 orElseThrow(() -> new NotFoundException("SAML 2.0 IdP '" + idpEntityID + '\''));
278
279
280 SAML2Client saml2Client = getSAML2Client(saml2ClientCacheLogin, idp, spEntityID, urlContext);
281
282 getRequestedAuthnContextProvider(idp).ifPresent(requestedAuthnContextProvider -> {
283 RequestedAuthnContext requestedAuthnContext = requestedAuthnContextProvider.get();
284 saml2Client.setRedirectionActionBuilder(new SAML2RedirectionActionBuilder(saml2Client) {
285
286 @Override
287 public Optional<RedirectionAction> getRedirectionAction(
288 final WebContext wc, final SessionStore sessionStore) {
289
290 this.saml2ObjectBuilder = new SAML2AuthnRequestBuilder() {
291
292 @Override
293 public AuthnRequest build(final SAML2MessageContext context) {
294 AuthnRequest authnRequest = super.build(context);
295 authnRequest.setRequestedAuthnContext(requestedAuthnContext);
296 return authnRequest;
297 }
298 };
299 return super.getRedirectionAction(wc, sessionStore);
300 }
301 });
302 });
303
304
305 SAML2SP4UIContext ctx = new SAML2SP4UIContext(
306 saml2Client.getConfiguration().getAuthnRequestBindingType(),
307 null);
308 RedirectionAction action = saml2Client.getRedirectionAction(ctx, NoOpSessionStore.INSTANCE).
309 orElseThrow(() -> {
310 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
311 sce.getElements().add("No RedirectionAction generated for AuthnRequest");
312 return sce;
313 });
314 return buildRequest(idpEntityID, action);
315 }
316
317 @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
318 public SAML2LoginResponse validateLoginResponse(final SAML2Response saml2Response) {
319
320 SAML2SP4UIIdP idp = Optional.ofNullable(idpDAO.findByEntityID(saml2Response.getIdpEntityID())).
321 orElseThrow(() -> new NotFoundException("SAML 2.0 IdP '" + saml2Response.getIdpEntityID() + '\''));
322
323
324 SAML2Client saml2Client = getSAML2Client(
325 saml2ClientCacheLogin,
326 idp,
327 saml2Response.getSpEntityID(),
328 saml2Response.getUrlContext());
329
330
331 SAML2Credentials credentials;
332 try {
333 SAML2SP4UIContext ctx = new SAML2SP4UIContext(
334 saml2Client.getConfiguration().getAuthnRequestBindingType(),
335 saml2Response);
336
337 credentials = (SAML2Credentials) saml2Client.getCredentialsExtractor().
338 extract(ctx, NoOpSessionStore.INSTANCE).
339 orElseThrow(() -> new IllegalStateException("No AuthnResponse found"));
340
341 saml2Client.getAuthenticator().validate(credentials, ctx, NoOpSessionStore.INSTANCE);
342 } catch (Exception e) {
343 LOG.error("While validating AuthnResponse", e);
344 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
345 sce.getElements().add(e.getMessage());
346 throw sce;
347 }
348
349
350 SAML2LoginResponse loginResp = new SAML2LoginResponse();
351 loginResp.setIdp(saml2Client.getIdentityProviderResolvedEntityId());
352 loginResp.setSloSupported(!(saml2Client.getLogoutActionBuilder() instanceof NoLogoutActionBuilder));
353
354 SAML2Credentials.SAMLNameID nameID = credentials.getNameId();
355
356 Item connObjectKeyItem = idp.getConnObjectKeyItem().orElse(null);
357
358 String keyValue = null;
359 if (StringUtils.isNotBlank(nameID.getValue())
360 && connObjectKeyItem != null
361 && connObjectKeyItem.getExtAttrName().equals(NameID.DEFAULT_ELEMENT_LOCAL_NAME)) {
362
363 keyValue = nameID.getValue();
364 }
365
366 loginResp.setNotOnOrAfter(new Date(credentials.getConditions().getNotOnOrAfter().toInstant().toEpochMilli()));
367
368 loginResp.setSessionIndex(credentials.getSessionIndex());
369
370 for (SAML2Credentials.SAMLAttribute attr : credentials.getAttributes()) {
371 if (!attr.getAttributeValues().isEmpty()) {
372 String attrName = Optional.ofNullable(attr.getFriendlyName()).orElse(attr.getName());
373 if (connObjectKeyItem != null && attrName.equals(connObjectKeyItem.getExtAttrName())) {
374 keyValue = attr.getAttributeValues().get(0);
375 }
376
377 loginResp.getAttrs().add(new Attr.Builder(attrName).values(attr.getAttributeValues()).build());
378 }
379 }
380
381 List<String> matchingUsers = Optional.ofNullable(keyValue).
382 map(k -> userManager.findMatchingUser(k, idp.getKey())).
383 orElse(List.of());
384 LOG.debug("Found {} matching users for {}", matchingUsers.size(), keyValue);
385
386 String username;
387 if (matchingUsers.isEmpty()) {
388 if (idp.isCreateUnmatching()) {
389 LOG.debug("No user matching {}, about to create", keyValue);
390
391 username = AuthContextUtils.callAsAdmin(AuthContextUtils.getDomain(),
392 () -> userManager.create(idp, loginResp, nameID.getValue()));
393 } else if (idp.isSelfRegUnmatching()) {
394 loginResp.setNameID(nameID.getValue());
395 UserTO userTO = new UserTO();
396
397 userManager.fill(idp.getKey(), loginResp, userTO);
398
399 loginResp.getAttrs().clear();
400 loginResp.getAttrs().addAll(userTO.getPlainAttrs());
401 if (StringUtils.isNotBlank(userTO.getUsername())) {
402 loginResp.setUsername(userTO.getUsername());
403 } else {
404 loginResp.setUsername(keyValue);
405 }
406
407 loginResp.setSelfReg(true);
408
409 return loginResp;
410 } else {
411 throw new NotFoundException("User matching the provided value " + keyValue);
412 }
413 } else if (matchingUsers.size() > 1) {
414 throw new IllegalArgumentException("Several users match the provided value " + keyValue);
415 } else {
416 if (idp.isUpdateMatching()) {
417 LOG.debug("About to update {} for {}", matchingUsers.get(0), keyValue);
418
419 username = AuthContextUtils.callAsAdmin(AuthContextUtils.getDomain(),
420 () -> userManager.update(matchingUsers.get(0), idp, loginResp));
421 } else {
422 username = matchingUsers.get(0);
423 }
424 }
425
426 loginResp.setUsername(username);
427 loginResp.setNameID(nameID.getValue());
428
429
430 Map<String, Object> claims = new HashMap<>();
431 claims.put(JWT_CLAIM_IDP_ENTITYID, idp.getEntityID());
432 claims.put(JWT_CLAIM_NAMEID_FORMAT, nameID.getFormat());
433 claims.put(JWT_CLAIM_NAMEID_VALUE, nameID.getValue());
434 claims.put(JWT_CLAIM_SESSIONINDEX, loginResp.getSessionIndex());
435
436 byte[] authorities = null;
437 try {
438 authorities = ENCRYPTOR.encode(POJOHelper.serialize(
439 authDataAccessor.getAuthorities(loginResp.getUsername(), null)), CipherAlgorithm.AES).getBytes();
440 } catch (Exception e) {
441 LOG.error("Could not fetch authorities", e);
442 }
443
444 Pair<String, OffsetDateTime> accessTokenInfo =
445 accessTokenDataBinder.create(loginResp.getUsername(), claims, authorities, true);
446 loginResp.setAccessToken(accessTokenInfo.getLeft());
447 loginResp.setAccessTokenExpiryTime(accessTokenInfo.getRight());
448
449 return loginResp;
450 }
451
452 @PreAuthorize("isAuthenticated() and not(hasRole('" + IdRepoEntitlement.ANONYMOUS + "'))")
453 public SAML2Request createLogoutRequest(
454 final String accessToken,
455 final String spEntityID,
456 final String urlContext) {
457
458
459 JWTClaimsSet claimsSet;
460 try {
461 SignedJWT jwt = SignedJWT.parse(accessToken);
462 claimsSet = jwt.getJWTClaimsSet();
463 } catch (ParseException e) {
464 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidAccessToken);
465 sce.getElements().add(e.getMessage());
466 throw sce;
467 }
468
469
470 String idpEntityID = (String) claimsSet.getClaim(JWT_CLAIM_IDP_ENTITYID);
471 if (idpEntityID == null) {
472 throw new NotFoundException("No SAML 2.0 IdP information found in the access token");
473 }
474 SAML2Client saml2Client = getSAML2Client(saml2ClientCacheLogout, idpEntityID, spEntityID, urlContext);
475 if (saml2Client.getLogoutActionBuilder() instanceof NoLogoutActionBuilder) {
476 throw new IllegalArgumentException("No SingleLogoutService available for "
477 + saml2Client.getIdentityProviderResolvedEntityId());
478 }
479
480
481 SAML2Profile saml2Profile = new SAML2Profile();
482 saml2Profile.setId((String) claimsSet.getClaim(JWT_CLAIM_NAMEID_VALUE));
483 saml2Profile.addAuthenticationAttribute(
484 SAML2Authenticator.SAML_NAME_ID_FORMAT,
485 claimsSet.getClaim(JWT_CLAIM_NAMEID_FORMAT));
486 saml2Profile.addAuthenticationAttribute(
487 SAML2Authenticator.SESSION_INDEX,
488 claimsSet.getClaim(JWT_CLAIM_SESSIONINDEX));
489
490 SAML2SP4UIContext ctx = new SAML2SP4UIContext(
491 saml2Client.getConfiguration().getSpLogoutRequestBindingType(), null);
492 RedirectionAction action = saml2Client.getLogoutAction(
493 ctx,
494 NoOpSessionStore.INSTANCE,
495 saml2Profile, null).
496 orElseThrow(() -> {
497 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Unknown);
498 sce.getElements().add("No RedirectionAction generated for LogoutRequest");
499 return sce;
500 });
501 return buildRequest(idpEntityID, action);
502 }
503
504 @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
505 public void validateLogoutResponse(final SAML2Response saml2Response) {
506
507 if (saml2Response.getIdpEntityID() == null) {
508 LOG.error("No SAML 2.0 IdP entityID provided, ignoring");
509 return;
510 }
511 SAML2Client saml2Client = getSAML2Client(
512 saml2ClientCacheLogout,
513 saml2Response.getIdpEntityID(),
514 saml2Response.getSpEntityID(),
515 saml2Response.getUrlContext());
516
517 Optional.ofNullable(idpDAO.findByEntityID(saml2Client.getIdentityProviderResolvedEntityId())).
518 orElseThrow(() -> new NotFoundException(
519 "SAML 2.0 IdP '" + saml2Client.getIdentityProviderResolvedEntityId() + '\''));
520
521
522 SAML2SP4UIContext ctx = new SAML2SP4UIContext(
523 saml2Client.getConfiguration().getSpLogoutRequestBindingType(),
524 saml2Response);
525
526 LogoutResponse logoutResponse;
527 try {
528 SAML2MessageContext saml2Ctx = saml2Client.getContextProvider().
529 buildContext(saml2Client, ctx, NoOpSessionStore.INSTANCE);
530 saml2Client.getLogoutProfileHandler().receive(saml2Ctx);
531
532 logoutResponse = (LogoutResponse) saml2Ctx.getMessageContext().getMessage();
533 } catch (Exception e) {
534 LOG.error("Could not validate LogoutResponse", e);
535 return;
536 }
537
538
539 if (!StatusCode.SUCCESS.equals(logoutResponse.getStatus().getStatusCode().getValue())) {
540 LOG.warn("Logout from SAML 2.0 IdP '{}' was not successful",
541 saml2Client.getIdentityProviderResolvedEntityId());
542 }
543 }
544
545 @Override
546 protected EntityTO resolveReference(
547 final Method method, final Object... args) throws UnresolvedReferenceException {
548
549 throw new UnresolvedReferenceException();
550 }
551 }