1616// under the License.
1717package org .apache .cloudstack .api .command ;
1818
19- import com .cloud .api .response .ApiResponseSerializer ;
20- import com .cloud .exception .CloudAuthenticationException ;
21- import com .cloud .user .Account ;
22- import com .cloud .user .DomainManager ;
23- import com .cloud .user .UserAccount ;
24- import com .cloud .user .UserAccountVO ;
25- import com .cloud .user .dao .UserAccountDao ;
26- import com .cloud .utils .db .EntityManager ;
19+ import java .io .IOException ;
20+ import java .net .InetAddress ;
21+ import java .util .List ;
22+ import java .util .Map ;
23+
24+ import javax .inject .Inject ;
25+ import javax .servlet .http .HttpServletRequest ;
26+ import javax .servlet .http .HttpServletResponse ;
27+ import javax .servlet .http .HttpSession ;
28+ import javax .xml .parsers .ParserConfigurationException ;
29+ import javax .xml .stream .FactoryConfigurationError ;
30+
2731import org .apache .cloudstack .api .APICommand ;
2832import org .apache .cloudstack .api .ApiConstants ;
2933import org .apache .cloudstack .api .ApiErrorCode ;
3539import org .apache .cloudstack .api .auth .APIAuthenticator ;
3640import org .apache .cloudstack .api .auth .PluggableAPIAuthenticator ;
3741import org .apache .cloudstack .api .response .LoginCmdResponse ;
42+ import org .apache .cloudstack .framework .config .ConfigKey ;
43+ import org .apache .cloudstack .framework .config .Configurable ;
3844import org .apache .cloudstack .saml .SAML2AuthManager ;
3945import org .apache .cloudstack .saml .SAMLPluginConstants ;
4046import org .apache .cloudstack .saml .SAMLProviderMetadata ;
4147import org .apache .cloudstack .saml .SAMLTokenVO ;
4248import org .apache .cloudstack .saml .SAMLUtils ;
49+ import org .apache .commons .lang .StringUtils ;
4350import org .apache .log4j .Logger ;
4451import org .opensaml .DefaultBootstrap ;
4552import org .opensaml .saml2 .core .Assertion ;
6269import org .opensaml .xml .validation .ValidationException ;
6370import org .xml .sax .SAXException ;
6471
65- import javax .inject .Inject ;
66- import javax .servlet .http .HttpServletRequest ;
67- import javax .servlet .http .HttpServletResponse ;
68- import javax .servlet .http .HttpSession ;
69- import javax .xml .parsers .ParserConfigurationException ;
70- import javax .xml .stream .FactoryConfigurationError ;
71- import java .io .IOException ;
72- import java .net .InetAddress ;
73- import java .util .List ;
74- import java .util .Map ;
72+ import com .cloud .api .response .ApiResponseSerializer ;
73+ import com .cloud .exception .CloudAuthenticationException ;
74+ import com .cloud .user .Account ;
75+ import com .cloud .user .DomainManager ;
76+ import com .cloud .user .UserAccount ;
77+ import com .cloud .user .UserAccountVO ;
78+ import com .cloud .user .dao .UserAccountDao ;
79+ import com .cloud .utils .db .EntityManager ;
7580
7681@ APICommand (name = "samlSso" , description = "SP initiated SAML Single Sign On" , requestHasSensitiveInfo = true , responseObject = LoginCmdResponse .class , entityType = {})
77- public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
82+ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator , Configurable {
7883 public static final Logger s_logger = Logger .getLogger (SAML2LoginAPIAuthenticatorCmd .class .getName ());
7984 private static final String s_name = "loginresponse" ;
8085
@@ -93,6 +98,9 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
9398 @ Inject
9499 private UserAccountDao _userAccountDao ;
95100
101+ private static final ConfigKey <String > Saml2FailedLoginRedirectUrl = new ConfigKey <String >("Advanced" , String .class , "saml2.failed.login.redirect.url" , "" ,
102+ "The URL to redirect the SAML2 login failed message (the default vaulue is empty)." , true );
103+
96104 SAML2AuthManager _samlAuthManager ;
97105
98106 /////////////////////////////////////////////////////
@@ -170,7 +178,7 @@ public String authenticate(final String command, final Map<String, Object[]> par
170178 throw new ServerApiException (ApiErrorCode .PARAM_ERROR , _apiServer .getSerializedApiError (ApiErrorCode .PARAM_ERROR .getHttpCode (),
171179 "IdP ID (" + idpId + ") has no Single Sign On URL defined please contact "
172180 + idpMetadata .getContactPersonName () + " <" + idpMetadata .getContactPersonEmail () + ">, cannot proceed." ,
173- params , responseType ));
181+ params , responseType ));
174182 }
175183 String authnId = SAMLUtils .generateSecureRandomId ();
176184 _samlAuthManager .saveToken (authnId , domainPath , idpMetadata .getEntityId ());
@@ -302,11 +310,7 @@ public String authenticate(final String command, final Map<String, Object[]> par
302310 }
303311 }
304312
305- if (userAccount == null || userAccount .getExternalEntity () == null || !_samlAuthManager .isUserAuthorized (userAccount .getId (), issuer .getValue ())) {
306- throw new ServerApiException (ApiErrorCode .ACCOUNT_ERROR , _apiServer .getSerializedApiError (ApiErrorCode .ACCOUNT_ERROR .getHttpCode (),
307- "Your authenticated user is not authorized for SAML Single Sign-On, please contact your administrator" ,
308- params , responseType ));
309- }
313+ whenFailToAuthenticateThrowExceptionOrRedirectToUrl (params , responseType , resp , issuer , userAccount );
310314
311315 try {
312316 if (_apiServer .verifyUser (userAccount .getId ())) {
@@ -329,6 +333,24 @@ public String authenticate(final String command, final Map<String, Object[]> par
329333 params , responseType ));
330334 }
331335
336+ /**
337+ * If it fails to authenticate the user, the method gets the value from configuration
338+ * Saml2FailedLoginRedirectUrl; if the user configured an error URL then it redirects to that
339+ * URL, otherwise it throws the ServerApiException
340+ */
341+ protected void whenFailToAuthenticateThrowExceptionOrRedirectToUrl (final Map <String , Object []> params , final String responseType , final HttpServletResponse resp , Issuer issuer ,
342+ UserAccount userAccount ) throws IOException {
343+ if (userAccount == null || userAccount .getExternalEntity () == null || !_samlAuthManager .isUserAuthorized (userAccount .getId (), issuer .getValue ())) {
344+ String saml2FailedLoginRedirectUrl = Saml2FailedLoginRedirectUrl .value (); //TODO configDao.getValue(Config.Saml2FailedLoginRedirectUrl.toString());
345+ if (StringUtils .isBlank (saml2FailedLoginRedirectUrl )) {
346+ throw new ServerApiException (ApiErrorCode .ACCOUNT_ERROR , _apiServer .getSerializedApiError (ApiErrorCode .ACCOUNT_ERROR .getHttpCode (),
347+ "Your authenticated user is not authorized for SAML Single Sign-On, please contact your administrator" , params , responseType ));
348+ } else {
349+ resp .sendRedirect (saml2FailedLoginRedirectUrl );
350+ }
351+ }
352+ }
353+
332354 @ Override
333355 public APIAuthenticationType getAPIType () {
334356 return APIAuthenticationType .LOGIN_API ;
@@ -345,4 +367,14 @@ public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
345367 s_logger .error ("No suitable Pluggable Authentication Manager found for SAML2 Login Cmd" );
346368 }
347369 }
370+
371+ @ Override
372+ public String getConfigComponentName () {
373+ return SAML2LoginAPIAuthenticatorCmd .class .getSimpleName ();
374+ }
375+
376+ @ Override
377+ public ConfigKey <?>[] getConfigKeys () {
378+ return new ConfigKey <?>[] { Saml2FailedLoginRedirectUrl };
379+ }
348380}
0 commit comments