Skip to content

Commit 71bc088

Browse files
authored
Improve login time (apache#6412)
* Improve slow login * Address review * Address Daan's review * Address Daniel reviews
1 parent 5dc86ad commit 71bc088

File tree

13 files changed

+581
-214
lines changed

13 files changed

+581
-214
lines changed

api/src/main/java/org/apache/cloudstack/acl/APIChecker.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,26 @@
2121
import com.cloud.user.User;
2222
import com.cloud.utils.component.Adapter;
2323

24-
// APIChecker checks the ownership and access control to API requests
24+
import java.util.List;
25+
26+
/**
27+
* APICheckers is designed to verify the ownership of resources and to control the access to APIs.
28+
*/
2529
public interface APIChecker extends Adapter {
2630
// Interface for checking access for a role using apiname
2731
// If true, apiChecker has checked the operation
2832
// If false, apiChecker is unable to handle the operation or not implemented
2933
// On exception, checkAccess failed don't allow
3034
boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException;
3135
boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException;
36+
/**
37+
* Verifies if the account has permission for the given list of APIs and returns only the allowed ones.
38+
*
39+
* @param role of the user to be verified
40+
* @param user to be verified
41+
* @param apiNames the list of apis to be verified
42+
* @return the list of allowed apis for the given user
43+
*/
44+
List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException;
3245
boolean isEnabled();
3346
}

engine/schema/src/main/java/com/cloud/projects/ProjectVO.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import org.apache.cloudstack.api.Identity;
3232
import org.apache.cloudstack.api.InternalIdentity;
33+
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
3334

3435
import com.cloud.utils.NumbersUtil;
3536
import com.cloud.utils.db.GenericDao;
@@ -116,9 +117,7 @@ public Date getRemoved() {
116117

117118
@Override
118119
public String toString() {
119-
StringBuilder buf = new StringBuilder("Project[");
120-
buf.append(id).append("|name=").append(name).append("|domainid=").append(domainId).append("]");
121-
return buf.toString();
120+
return String.format("Project %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "name", "uuid", "domainId"));
122121
}
123122

124123
@Override

engine/schema/src/main/java/com/cloud/user/AccountVO.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.cloud.utils.db.GenericDao;
2020
import org.apache.cloudstack.acl.RoleType;
21+
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
2122

2223
import javax.persistence.Column;
2324
import javax.persistence.Entity;
@@ -189,7 +190,7 @@ public long getAccountId() {
189190

190191
@Override
191192
public String toString() {
192-
return String.format("Acct[%s-%s] -- Account {\"id\": %s, \"name\": \"%s\", \"uuid\": \"%s\"}", uuid, accountName, id, accountName, uuid);
193+
return String.format("Account [%s]", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid","accountName", "id"));
193194
}
194195

195196
@Override

engine/schema/src/main/java/com/cloud/user/UserVO.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import org.apache.cloudstack.api.Identity;
3232
import org.apache.cloudstack.api.InternalIdentity;
33+
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
3334

3435
import com.cloud.user.Account.State;
3536
import com.cloud.utils.db.Encrypt;
@@ -283,7 +284,7 @@ public void setRegistered(boolean registered) {
283284

284285
@Override
285286
public String toString() {
286-
return new StringBuilder("User[").append(id).append("-").append(username).append("]").toString();
287+
return String.format("User %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "username", "uuid"));
287288
}
288289

289290
@Override

engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.cloudstack.acl;
1919

2020
import com.cloud.utils.db.GenericDao;
21+
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
2122

2223
import javax.persistence.Column;
2324
import javax.persistence.Entity;
@@ -114,4 +115,9 @@ public void setDescription(String description) {
114115
public boolean isDefault() {
115116
return isDefault;
116117
}
118+
119+
@Override
120+
public String toString() {
121+
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "name", "uuid", "roleType");
122+
}
117123
}

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

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717
package org.apache.cloudstack.acl;
1818

19+
import java.util.ArrayList;
1920
import java.util.HashMap;
2021
import java.util.HashSet;
2122
import java.util.List;
@@ -48,7 +49,7 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
4849
private List<PluggableService> services;
4950
private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
5051

51-
private static final Logger logger = Logger.getLogger(DynamicRoleBasedAPIAccessChecker.class.getName());
52+
private static final Logger LOGGER = Logger.getLogger(DynamicRoleBasedAPIAccessChecker.class.getName());
5253

5354
protected DynamicRoleBasedAPIAccessChecker() {
5455
super();
@@ -57,22 +58,58 @@ protected DynamicRoleBasedAPIAccessChecker() {
5758
}
5859
}
5960

60-
private void denyApiAccess(final String commandName) throws PermissionDeniedException {
61-
throw new PermissionDeniedException("The API " + commandName + " is denied for the account's role.");
61+
@Override
62+
public List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException {
63+
if (!isEnabled()) {
64+
return apiNames;
65+
}
66+
67+
List<RolePermission> allPermissions = roleService.findAllPermissionsBy(role.getId());
68+
List<String> allowedApis = new ArrayList<>();
69+
for (String api : apiNames) {
70+
if (checkApiPermissionByRole(role, api, allPermissions)) {
71+
allowedApis.add(api);
72+
}
73+
}
74+
return allowedApis;
6275
}
6376

64-
public boolean isDisabled() {
65-
return !roleService.isEnabled();
77+
/**
78+
* Checks if the given Role of an Account has the allowed permission for the given API.
79+
*
80+
* @param role to be used on the verification
81+
* @param apiName to be verified
82+
* @param allPermissions list of role permissions for the given role
83+
* @return if the role has the permission for the API
84+
*/
85+
public boolean checkApiPermissionByRole(Role role, String apiName, List<RolePermission> allPermissions) {
86+
for (final RolePermission permission : allPermissions) {
87+
if (!permission.getRule().matches(apiName)) {
88+
continue;
89+
}
90+
91+
if (!Permission.ALLOW.equals(permission.getPermission())) {
92+
return false;
93+
}
94+
95+
if (LOGGER.isTraceEnabled()) {
96+
LOGGER.trace(String.format("The API [%s] is allowed for the role %s by the permission [%s].", apiName, role, permission.getRule().toString()));
97+
}
98+
return true;
99+
}
100+
return annotationRoleBasedApisMap.get(role.getRoleType()) != null &&
101+
annotationRoleBasedApisMap.get(role.getRoleType()).contains(apiName);
66102
}
67103

68104
@Override
69105
public boolean checkAccess(User user, String commandName) throws PermissionDeniedException {
70-
if (isDisabled()) {
106+
if (!isEnabled()) {
71107
return true;
72108
}
109+
73110
Account account = accountService.getAccount(user.getAccountId());
74111
if (account == null) {
75-
throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null");
112+
throw new PermissionDeniedException(String.format("The account id [%s] for user id [%s] is null.", user.getAccountId(), user.getUuid()));
76113
}
77114

78115
return checkAccess(account, commandName);
@@ -81,37 +118,32 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie
81118
public boolean checkAccess(Account account, String commandName) {
82119
final Role accountRole = roleService.findRole(account.getRoleId());
83120
if (accountRole == null || accountRole.getId() < 1L) {
84-
denyApiAccess(commandName);
121+
throw new PermissionDeniedException(String.format("The account [%s] has role null or unknown.", account));
85122
}
86123

87-
// Allow all APIs for root admins
88124
if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId()) {
125+
LOGGER.info(String.format("Account [%s] is Root Admin or Domain Admin, all APIs are allowed.", account));
89126
return true;
90127
}
91128

92-
// Check against current list of permissions
93-
for (final RolePermission permission : roleService.findAllPermissionsBy(accountRole.getId())) {
94-
if (permission.getRule().matches(commandName)) {
95-
if (Permission.ALLOW.equals(permission.getPermission())) {
96-
return true;
97-
} else {
98-
denyApiAccess(commandName);
99-
}
100-
}
101-
}
102-
103-
// Check annotations
104-
if (annotationRoleBasedApisMap.get(accountRole.getRoleType()) != null
105-
&& annotationRoleBasedApisMap.get(accountRole.getRoleType()).contains(commandName)) {
129+
List<RolePermission> allPermissions = roleService.findAllPermissionsBy(accountRole.getId());
130+
if (checkApiPermissionByRole(accountRole, commandName, allPermissions)) {
106131
return true;
107132
}
108-
109-
// Default deny all
110-
throw new UnavailableCommandException("The API " + commandName + " does not exist or is not available for this account.");
133+
throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for the account %s.", commandName, account));
111134
}
112135

136+
/**
137+
* Only one strategy should be used between StaticRoleBasedAPIAccessChecker and DynamicRoleBasedAPIAccessChecker
138+
* Default behavior is to use the Dynamic version. The StaticRoleBasedAPIAccessChecker is the legacy version.
139+
* If roleService is enabled, then it uses the DynamicRoleBasedAPIAccessChecker, otherwise, it will use the
140+
* StaticRoleBasedAPIAccessChecker.
141+
*/
113142
@Override
114143
public boolean isEnabled() {
144+
if (!roleService.isEnabled()) {
145+
LOGGER.trace("RoleService is disabled. We will not use DynamicRoleBasedAPIAccessChecker.");
146+
}
115147
return roleService.isEnabled();
116148
}
117149

0 commit comments

Comments
 (0)