1+ // Licensed to the Apache Software Foundation (ASF) under one
2+ // or more contributor license agreements. See the NOTICE file
3+ // distributed with this work for additional information
4+ // regarding copyright ownership. The ASF licenses this file
5+ // to you under the Apache License, Version 2.0 (the
6+ // "License"); you may not use this file except in compliance
7+ // with the License. You may obtain a copy of the License at
8+ //
9+ // http://www.apache.org/licenses/LICENSE-2.0
10+ //
11+ // Unless required by applicable law or agreed to in writing,
12+ // software distributed under the License is distributed on an
13+ // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+ // KIND, either express or implied. See the License for the
15+ // specific language governing permissions and limitations
16+ // under the License.
17+ package org .apache .cloudstack .api .command ;
18+
19+ import com .cloud .api .response .ApiResponseSerializer ;
20+ import com .cloud .domain .Domain ;
21+ import com .cloud .domain .dao .DomainDao ;
22+ import com .cloud .user .Account ;
23+ import com .cloud .user .User ;
24+ import com .cloud .user .UserAccount ;
25+ import com .cloud .user .UserAccountVO ;
26+ import com .cloud .user .dao .UserAccountDao ;
27+ import com .cloud .user .dao .UserDao ;
28+ import com .cloud .utils .HttpUtils ;
29+ import org .apache .cloudstack .api .APICommand ;
30+ import org .apache .cloudstack .api .ApiConstants ;
31+ import org .apache .cloudstack .api .ApiErrorCode ;
32+ import org .apache .cloudstack .api .ApiServerService ;
33+ import org .apache .cloudstack .api .BaseCmd ;
34+ import org .apache .cloudstack .api .Parameter ;
35+ import org .apache .cloudstack .api .ServerApiException ;
36+ import org .apache .cloudstack .api .auth .APIAuthenticationType ;
37+ import org .apache .cloudstack .api .auth .APIAuthenticator ;
38+ import org .apache .cloudstack .api .auth .PluggableAPIAuthenticator ;
39+ import org .apache .cloudstack .api .response .DomainResponse ;
40+ import org .apache .cloudstack .api .response .ListResponse ;
41+ import org .apache .cloudstack .api .response .LoginCmdResponse ;
42+ import org .apache .cloudstack .api .response .SamlUserAccountResponse ;
43+ import org .apache .cloudstack .api .response .SuccessResponse ;
44+ import org .apache .cloudstack .api .response .UserResponse ;
45+ import org .apache .cloudstack .saml .SAML2AuthManager ;
46+ import org .apache .cloudstack .saml .SAMLUtils ;
47+ import org .apache .log4j .Logger ;
48+
49+ import javax .inject .Inject ;
50+ import javax .servlet .http .HttpServletRequest ;
51+ import javax .servlet .http .HttpServletResponse ;
52+ import javax .servlet .http .HttpSession ;
53+ import java .util .ArrayList ;
54+ import java .util .List ;
55+ import java .util .Map ;
56+
57+ @ APICommand (name = "listAndSwitchSamlAccount" , description = "Lists and switches to other SAML accounts owned by the SAML user" , responseObject = SuccessResponse .class , requestHasSensitiveInfo = false , responseHasSensitiveInfo = false )
58+ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthenticator {
59+ public static final Logger s_logger = Logger .getLogger (ListAndSwitchSAMLAccountCmd .class .getName ());
60+ private static final String s_name = "listandswitchsamlaccountresponse" ;
61+
62+ @ Inject
63+ ApiServerService _apiServer ;
64+
65+ @ Inject
66+ private UserAccountDao _userAccountDao ;
67+ @ Inject
68+ private UserDao _userDao ;
69+ @ Inject
70+ private DomainDao _domainDao ;
71+
72+ SAML2AuthManager _samlAuthManager ;
73+
74+ /////////////////////////////////////////////////////
75+ //////////////// API parameters /////////////////////
76+ /////////////////////////////////////////////////////
77+
78+ @ Parameter (name = ApiConstants .USER_ID , type = CommandType .UUID , entityType = UserResponse .class , required = false , description = "User uuid" )
79+ private Long userId ;
80+
81+ @ Parameter (name = ApiConstants .DOMAIN_ID , type = CommandType .UUID , entityType = DomainResponse .class , required = false , description = "Domain uuid" )
82+ private Long domainId ;
83+
84+ @ Override
85+ public String getCommandName () {
86+ return s_name ;
87+ }
88+
89+ @ Override
90+ public long getEntityOwnerId () {
91+ return Account .ACCOUNT_ID_SYSTEM ;
92+ }
93+
94+ @ Override
95+ public void execute () {
96+ throw new ServerApiException (ApiErrorCode .METHOD_NOT_ALLOWED , "This is an authentication plugin api, cannot be used directly" );
97+ }
98+
99+ @ Override
100+ public String authenticate (final String command , final Map <String , Object []> params , final HttpSession session , final String remoteAddress , final String responseType , final StringBuilder auditTrailSb , final HttpServletRequest req , final HttpServletResponse resp ) throws ServerApiException {
101+ if (session == null || session .isNew ()) {
102+ throw new ServerApiException (ApiErrorCode .UNAUTHORIZED , _apiServer .getSerializedApiError (ApiErrorCode .UNAUTHORIZED .getHttpCode (),
103+ "Only authenticated saml users can request this API" ,
104+ params , responseType ));
105+ }
106+
107+ if (!HttpUtils .validateSessionKey (session , params , req .getCookies (), ApiConstants .SESSIONKEY )) {
108+ throw new ServerApiException (ApiErrorCode .UNAUTHORIZED , _apiServer .getSerializedApiError (ApiErrorCode .UNAUTHORIZED .getHttpCode (),
109+ "Unauthorized session, please re-login" ,
110+ params , responseType ));
111+ }
112+
113+ final long currentUserId = (Long ) session .getAttribute ("userid" );
114+ final UserAccount currentUserAccount = _accountService .getUserAccountById (currentUserId );
115+ if (currentUserAccount == null || currentUserAccount .getSource () != User .Source .SAML2 ) {
116+ throw new ServerApiException (ApiErrorCode .ACCOUNT_ERROR , _apiServer .getSerializedApiError (ApiErrorCode .ACCOUNT_ERROR .getHttpCode (),
117+ "Only authenticated saml users can request this API" ,
118+ params , responseType ));
119+ }
120+
121+ String userUuid = null ;
122+ String domainUuid = null ;
123+ if (params .containsKey (ApiConstants .USER_ID )) {
124+ userUuid = ((String [])params .get (ApiConstants .USER_ID ))[0 ];
125+ }
126+ if (params .containsKey (ApiConstants .DOMAIN_ID )) {
127+ domainUuid = ((String [])params .get (ApiConstants .DOMAIN_ID ))[0 ];
128+ }
129+
130+ if (userUuid != null && domainUuid != null ) {
131+ final User user = _userDao .findByUuid (userUuid );
132+ final Domain domain = _domainDao .findByUuid (domainUuid );
133+ final UserAccount nextUserAccount = _accountService .getUserAccountById (user .getId ());
134+ if (!nextUserAccount .getUsername ().equals (currentUserAccount .getUsername ())
135+ || !nextUserAccount .getExternalEntity ().equals (currentUserAccount .getExternalEntity ())
136+ || (nextUserAccount .getDomainId () != domain .getId ())
137+ || (nextUserAccount .getSource () != User .Source .SAML2 )) {
138+ throw new ServerApiException (ApiErrorCode .PARAM_ERROR , _apiServer .getSerializedApiError (ApiErrorCode .PARAM_ERROR .getHttpCode (),
139+ "User account is not allowed to switch to the requested account" ,
140+ params , responseType ));
141+ }
142+ try {
143+ if (_apiServer .verifyUser (nextUserAccount .getId ())) {
144+ final LoginCmdResponse loginResponse = (LoginCmdResponse ) _apiServer .loginUser (session , nextUserAccount .getUsername (), nextUserAccount .getUsername () + nextUserAccount .getSource ().toString (),
145+ nextUserAccount .getDomainId (), null , remoteAddress , params );
146+ SAMLUtils .setupSamlUserCookies (loginResponse , resp );
147+ resp .sendRedirect (SAML2AuthManager .SAMLCloudStackRedirectionUrl .value ());
148+ return ApiResponseSerializer .toSerializedString (loginResponse , responseType );
149+ }
150+ } catch (final Exception ignored ) {
151+ }
152+ } else {
153+ List <UserAccountVO > switchableAccounts = _userAccountDao .getAllUsersByNameAndEntity (currentUserAccount .getUsername (), currentUserAccount .getExternalEntity ());
154+ if (switchableAccounts != null && switchableAccounts .size () > 0 && currentUserId != User .UID_SYSTEM ) {
155+ List <SamlUserAccountResponse > accountResponses = new ArrayList <SamlUserAccountResponse >();
156+ for (UserAccountVO userAccount : switchableAccounts ) {
157+ User user = _userDao .getUser (userAccount .getId ());
158+ Domain domain = _domainService .getDomain (userAccount .getDomainId ());
159+ SamlUserAccountResponse accountResponse = new SamlUserAccountResponse ();
160+ accountResponse .setUserId (user .getUuid ());
161+ accountResponse .setUserName (user .getUsername ());
162+ accountResponse .setDomainId (domain .getUuid ());
163+ accountResponse .setDomainName (domain .getName ());
164+ accountResponse .setAccountName (userAccount .getAccountName ());
165+ accountResponse .setIdpId (user .getExternalEntity ());
166+ accountResponses .add (accountResponse );
167+ }
168+ ListResponse <SamlUserAccountResponse > response = new ListResponse <SamlUserAccountResponse >();
169+ response .setResponses (accountResponses );
170+ response .setResponseName (getCommandName ());
171+ return ApiResponseSerializer .toSerializedString (response , responseType );
172+ }
173+ }
174+ throw new ServerApiException (ApiErrorCode .ACCOUNT_ERROR , _apiServer .getSerializedApiError (ApiErrorCode .ACCOUNT_ERROR .getHttpCode (),
175+ "Unable to switch to requested SAML account. Please make sure your user/account is enabled. Please contact your administrator." ,
176+ params , responseType ));
177+ }
178+
179+ @ Override
180+ public APIAuthenticationType getAPIType () {
181+ return APIAuthenticationType .READONLY_API ;
182+ }
183+
184+ @ Override
185+ public void setAuthenticators (List <PluggableAPIAuthenticator > authenticators ) {
186+ for (PluggableAPIAuthenticator authManager : authenticators ) {
187+ if (authManager != null && authManager instanceof SAML2AuthManager ) {
188+ _samlAuthManager = (SAML2AuthManager ) authManager ;
189+ }
190+ }
191+ if (_samlAuthManager == null ) {
192+ s_logger .error ("No suitable Pluggable Authentication Manager found for SAML2 listAndSwitchSamlAccount Cmd" );
193+ }
194+ }
195+ }
0 commit comments