Skip to content

Commit 9a13227

Browse files
olivierlemasleyadvr
authored andcommitted
CLOUDSTACK-10327: Do not invalidate the session when an API command is not available (#2498)
CloudStack SSO (using security.singlesignon.key) does not work anymore with CloudStack 4.11, since commit 9988c26, which introduced a regression due to a refactoring: every API request that is not "validated" generates the same error (401 - Unauthorized) and invalidates the session. However, CloudStack UI executes a call to listConfigurations in method bypassLoginCheck. A non-admin user does not have the permissions to execute this request, which causes an error 401: {"listconfigurationsresponse":{"uuidList":[],"errorcode":401,"errortext":"unable to verify user credentials and/or request signature"}} The session (already created by SSO) is then invalidated and the user cannot access to CloudStack UI (error "Session Expired"). Before 9988c26 (up to CloudStack 4.10), an error 432 was returned (and ignored): {"errorresponse":{"uuidList":[],"errorcode":432,"cserrorcode":9999,"errortext":"The user is not allowed to request the API command or the API command does not exist"}} Even if the call to listConfigurations was removed, another call to listIdps also lead to an error 401 for user accounts if the SAML plugin is not enabled. This pull request aims to fix the SSO issue, by restoring errors 432 (instead of 401 + invalidate session) for commands not available. However, if an API command is explicitly denied using ACLs or if the session key is incorrect, it still generates an error 401 and invalidates the session.
1 parent ea55a00 commit 9a13227

5 files changed

Lines changed: 57 additions & 5 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 com.cloud.exception;
18+
19+
import com.cloud.utils.SerialVersionUID;
20+
21+
public class UnavailableCommandException extends PermissionDeniedException {
22+
23+
private static final long serialVersionUID = SerialVersionUID.UnavailableCommandException;
24+
25+
protected UnavailableCommandException() {
26+
super();
27+
}
28+
29+
public UnavailableCommandException(String msg) {
30+
super(msg);
31+
}
32+
33+
public UnavailableCommandException(String msg, Throwable cause) {
34+
super(msg, cause);
35+
}
36+
}

plugins/acl/dynamic-role-based/src/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import javax.inject.Inject;
2626
import javax.naming.ConfigurationException;
2727

28+
import com.cloud.exception.UnavailableCommandException;
2829
import org.apache.cloudstack.api.APICommand;
2930

3031
import com.cloud.exception.PermissionDeniedException;
@@ -53,8 +54,7 @@ protected DynamicRoleBasedAPIAccessChecker() {
5354
}
5455

5556
private void denyApiAccess(final String commandName) throws PermissionDeniedException {
56-
throw new PermissionDeniedException("The API does not exist or is blacklisted for the account's role. " +
57-
"The account with is not allowed to request the api: " + commandName);
57+
throw new PermissionDeniedException("The API " + commandName + " is blacklisted for the account's role.");
5858
}
5959

6060
public boolean isDisabled() {
@@ -99,8 +99,7 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie
9999
}
100100

101101
// Default deny all
102-
denyApiAccess(commandName);
103-
return false;
102+
throw new UnavailableCommandException("The API " + commandName + " does not exist or is not available for this account.");
104103
}
105104

106105
public void addApiToRoleBasedAnnotationsMap(final RoleType roleType, final String commandName) {

plugins/acl/static-role-based/src/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import javax.inject.Inject;
2626
import javax.naming.ConfigurationException;
2727

28+
import com.cloud.exception.UnavailableCommandException;
2829
import org.apache.log4j.Logger;
2930

3031
import org.apache.cloudstack.api.APICommand;
@@ -45,6 +46,7 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIC
4546
protected static final Logger LOGGER = Logger.getLogger(StaticRoleBasedAPIAccessChecker.class);
4647

4748
private Set<String> commandPropertyFiles = new HashSet<String>();
49+
private Set<String> commandNames = new HashSet<String>();
4850
private Set<String> commandsPropertiesOverrides = new HashSet<String>();
4951
private Map<RoleType, Set<String>> commandsPropertiesRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
5052
private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
@@ -87,7 +89,11 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie
8789
return true;
8890
}
8991

90-
throw new PermissionDeniedException("The API does not exist or is blacklisted. Role type=" + roleType.toString() + " is not allowed to request the api: " + commandName);
92+
if (commandNames.contains(commandName)) {
93+
throw new PermissionDeniedException("The API is blacklisted. Role type=" + roleType.toString() + " is not allowed to request the api: " + commandName);
94+
} else {
95+
throw new UnavailableCommandException("The API " + commandName + " does not exist or is not available for this account.");
96+
}
9197
}
9298

9399
@Override
@@ -110,6 +116,9 @@ public boolean start() {
110116
if (!commands.contains(command.name()))
111117
commands.add(command.name());
112118
}
119+
if (!commandNames.contains(command.name())) {
120+
commandNames.add(command.name());
121+
}
113122
}
114123
}
115124
return super.start();
@@ -119,6 +128,9 @@ private void processMapping(Map<String, String> configMap) {
119128
for (Map.Entry<String, String> entry : configMap.entrySet()) {
120129
String apiName = entry.getKey();
121130
String roleMask = entry.getValue();
131+
if (!commandNames.contains(apiName)) {
132+
commandNames.add(apiName);
133+
}
122134
commandsPropertiesOverrides.add(apiName);
123135
try {
124136
short cmdPermissions = Short.parseShort(roleMask);

server/src/com/cloud/api/ApiServer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.cloud.exception.RequestLimitException;
3535
import com.cloud.exception.ResourceAllocationException;
3636
import com.cloud.exception.ResourceUnavailableException;
37+
import com.cloud.exception.UnavailableCommandException;
3738
import com.cloud.user.Account;
3839
import com.cloud.user.AccountManager;
3940
import com.cloud.user.DomainManager;
@@ -958,6 +959,9 @@ private boolean commandAvailable(final InetAddress remoteAddress, final String c
958959
} catch (final RequestLimitException ex) {
959960
s_logger.debug(ex.getMessage());
960961
throw new ServerApiException(ApiErrorCode.API_LIMIT_EXCEED, ex.getMessage());
962+
} catch (final UnavailableCommandException ex) {
963+
s_logger.debug(ex.getMessage());
964+
throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, ex.getMessage());
961965
} catch (final PermissionDeniedException ex) {
962966
final String errorMessage = "The given command '" + commandName + "' either does not exist, is not available" +
963967
" for user, or not available from ip address '" + remoteAddress + "'.";

utils/src/main/java/com/cloud/utils/SerialVersionUID.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,5 @@ public interface SerialVersionUID {
6868
public static final long NioConnectionException = Base | 0x2c;
6969
public static final long TaskExecutionException = Base | 0x2d;
7070
public static final long SnapshotBackupException = Base | 0x2e;
71+
public static final long UnavailableCommandException = Base | 0x2f;
7172
}

0 commit comments

Comments
 (0)