Skip to content

Commit

Permalink
Merge branch 'main' into healthcheck-main
Browse files Browse the repository at this point in the history
  • Loading branch information
weizhouapache authored Oct 14, 2024
2 parents cb24461 + 554ea22 commit 57e54ce
Show file tree
Hide file tree
Showing 29 changed files with 820 additions and 109 deletions.
2 changes: 2 additions & 0 deletions api/src/main/java/com/cloud/user/AccountService.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ User createUser(String userName, String password, String firstName, String lastN

void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) throws PermissionDeniedException;

void validateAccountHasAccessToResource(Account account, AccessType accessType, Object resource);

Long finalyzeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2029,6 +2029,9 @@ public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMach
MigrationOptions.Type migrationType = decideMigrationTypeAndCopyTemplateIfNeeded(destHost, vmInstance, srcVolumeInfo, sourceStoragePool, destStoragePool, destDataStore);
migrateNonSharedInc = migrateNonSharedInc || MigrationOptions.Type.LinkedClone.equals(migrationType);

MigrationOptions.Type migrationType = decideMigrationTypeAndCopyTemplateIfNeeded(destHost, vmInstance, srcVolumeInfo, sourceStoragePool, destStoragePool, destDataStore);
migrateNonSharedInc = migrateNonSharedInc || MigrationOptions.Type.LinkedClone.equals(migrationType);

VolumeVO destVolume = duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool);
VolumeInfo destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

import javax.inject.Inject;

import com.cloud.user.Account;

import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
Expand All @@ -40,6 +43,7 @@ public class QuotaBalanceCmd extends BaseCmd {
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Account Id for which statement needs to be generated")
private String accountName;

@ACL
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "If domain Id is given and the caller is domain admin then the statement is generated for domain.")
private Long domainId;

Expand All @@ -51,6 +55,7 @@ public class QuotaBalanceCmd extends BaseCmd {
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;

@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "List usage records for the specified account")
private Long accountId;

Expand Down Expand Up @@ -104,7 +109,14 @@ public void setStartDate(Date startDate) {

@Override
public long getEntityOwnerId() {
return _accountService.getActiveAccountByName(accountName, domainId).getAccountId();
if (accountId != null) {
return accountId;
}
Account account = _accountService.getActiveAccountByName(accountName, domainId);
if (account != null) {
return account.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.cloud.user.Account;

import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
Expand Down Expand Up @@ -46,6 +47,7 @@ public class QuotaCreditsCmd extends BaseCmd {
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Account Id for which quota credits need to be added")
private String accountName;

@ACL
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "Domain for which quota credits need to be added")
private Long domainId;

Expand Down Expand Up @@ -130,6 +132,10 @@ public void execute() {

@Override
public long getEntityOwnerId() {
Account account = _accountService.getActiveAccountByName(accountName, domainId);
if (account != null) {
return account.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import javax.inject.Inject;

import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
Expand All @@ -42,6 +43,7 @@ public class QuotaStatementCmd extends BaseCmd {
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Optional, Account Id for which statement needs to be generated")
private String accountName;

@ACL
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "Optional, If domain Id is given and the caller is domain admin then the statement is generated for domain.")
private Long domainId;

Expand All @@ -56,6 +58,7 @@ public class QuotaStatementCmd extends BaseCmd {
@Parameter(name = ApiConstants.TYPE, type = CommandType.INTEGER, description = "List quota usage records for the specified usage type")
private Integer usageType;

@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "List usage records for the specified account")
private Long accountId;

Expand Down Expand Up @@ -112,6 +115,9 @@ public void setStartDate(Date startDate) {

@Override
public long getEntityOwnerId() {
if (accountId != null) {
return accountId;
}
Account activeAccountByName = _accountService.getActiveAccountByName(accountName, domainId);
if (activeAccountByName != null) {
return activeAccountByName.getAccountId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,11 @@ public void checkAccess(Account account, AccessType accessType, boolean sameOwne
// TODO Auto-generated method stub
}

@Override
public void validateAccountHasAccessToResource(Account account, AccessType accessType, Object resource) {
// TODO Auto-generated method stub
}

@Override
public Long finalyzeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly) {
// TODO Auto-generated method stub
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
import org.apache.cloudstack.storage.object.BaseObjectStoreDriverImpl;
import org.apache.cloudstack.storage.object.BucketObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.twonote.rgwadmin4j.RgwAdmin;
import org.twonote.rgwadmin4j.RgwAdminBuilder;
import org.twonote.rgwadmin4j.model.BucketInfo;
Expand All @@ -62,7 +60,6 @@
import java.util.HashMap;

public class CephObjectStoreDriverImpl extends BaseObjectStoreDriverImpl {
private static final Logger s_logger = LogManager.getLogger(CephObjectStoreDriverImpl.class);

@Inject
AccountDao _accountDao;
Expand Down Expand Up @@ -168,7 +165,7 @@ public void setBucketPolicy(BucketTO bucket, String policy, long storeId) {
String policyConfig;

if (policy.equalsIgnoreCase("public")) {
s_logger.debug("Setting public policy on bucket " + bucket.getName());
logger.debug("Setting public policy on bucket " + bucket.getName());
StringBuilder builder = new StringBuilder();
builder.append("{\n");
builder.append(" \"Statement\": [\n");
Expand All @@ -192,7 +189,7 @@ public void setBucketPolicy(BucketTO bucket, String policy, long storeId) {
builder.append("}\n");
policyConfig = builder.toString();
} else {
s_logger.debug("Setting private policy on bucket " + bucket.getName());
logger.debug("Setting private policy on bucket " + bucket.getName());
policyConfig = "{\"Version\":\"2012-10-17\",\"Statement\":[]}";
}

Expand All @@ -218,15 +215,15 @@ public boolean createUser(long accountId, long storeId) {
RgwAdmin rgwAdmin = getRgwAdminClient(storeId);
String username = account.getUuid();

s_logger.debug("Attempting to create Ceph RGW user for account " + account.getAccountName() + " with UUID " + username);
logger.debug("Attempting to create Ceph RGW user for account " + account.getAccountName() + " with UUID " + username);
try {
Optional<User> user = rgwAdmin.getUserInfo(username);
if (user.isPresent()) {
s_logger.info("User already exists in Ceph RGW: " + username);
logger.info("User already exists in Ceph RGW: " + username);
return true;
}
} catch (Exception e) {
s_logger.debug("User does not exist. Creating user in Ceph RGW: " + username);
logger.debug("User does not exist. Creating user in Ceph RGW: " + username);
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

public class CephObjectStoreLifeCycleImpl implements ObjectStoreLifeCycle {

private static final Logger s_logger = LogManager.getLogger(CephObjectStoreLifeCycleImpl.class);
private Logger logger = LogManager.getLogger(CephObjectStoreLifeCycleImpl.class);

@Inject
ObjectStoreHelper objectStoreHelper;
Expand Down Expand Up @@ -72,7 +72,7 @@ public DataStore initialize(Map<String, Object> dsInfos) {
objectStoreParameters.put("accesskey", accessKey);
objectStoreParameters.put("secretkey", secretKey);

s_logger.info("Attempting to connect to Ceph RGW at " + url + " with access key " + accessKey);
logger.info("Attempting to connect to Ceph RGW at " + url + " with access key " + accessKey);

RgwAdmin rgwAdmin = new RgwAdminBuilder()
.accessKey(accessKey)
Expand All @@ -81,10 +81,10 @@ public DataStore initialize(Map<String, Object> dsInfos) {
.build();
try {
List<String> buckets = rgwAdmin.listBucket();
s_logger.debug("Found " + buckets + " buckets at Ceph RGW: " + url);
s_logger.info("Successfully connected to Ceph RGW: " + url);
logger.debug("Found " + buckets + " buckets at Ceph RGW: " + url);
logger.info("Successfully connected to Ceph RGW: " + url);
} catch (Exception e) {
s_logger.debug("Error while initializing Ceph RGW Object Store: " + e.getMessage());
logger.debug("Error while initializing Ceph RGW Object Store: " + e.getMessage());
throw new RuntimeException("Error while initializing Ceph RGW Object Store. Invalid credentials or URL");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.apache.cloudstack.saml.SAML2AuthManager;
import org.apache.cloudstack.saml.SAMLUtils;

import com.cloud.api.ApiServer;
import com.cloud.api.response.ApiResponseSerializer;
import com.cloud.domain.Domain;
import com.cloud.domain.dao.DomainDao;
Expand All @@ -59,6 +60,8 @@
import com.cloud.user.dao.UserDao;
import com.cloud.utils.HttpUtils;

import org.apache.commons.lang3.EnumUtils;

@APICommand(name = "listAndSwitchSamlAccount", description = "Lists and switches to other SAML accounts owned by the SAML user", responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthenticator {

Expand Down Expand Up @@ -102,7 +105,9 @@ public String authenticate(final String command, final Map<String, Object[]> par
params, responseType));
}

if (!HttpUtils.validateSessionKey(session, params, req.getCookies(), ApiConstants.SESSIONKEY)) {
HttpUtils.ApiSessionKeyCheckOption sessionKeyCheckOption = EnumUtils.getEnumIgnoreCase(HttpUtils.ApiSessionKeyCheckOption.class,
ApiServer.ApiSessionKeyCheckLocations.value(), HttpUtils.ApiSessionKeyCheckOption.CookieAndParameter);
if (!HttpUtils.validateSessionKey(session, params, req.getCookies(), ApiConstants.SESSIONKEY, sessionKeyCheckOption)) {
throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, _apiServer.getSerializedApiError(ApiErrorCode.UNAUTHORIZED.getHttpCode(),
"Unauthorized session, please re-login",
params, responseType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableSe
ConfigKey<Boolean> SAMLCheckSignature = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.check.signature", "true",
"When enabled (default and recommended), SAML2 signature checks are enforced and lack of signature in the SAML SSO response will cause login exception. Disabling this is not advisable but provided for backward compatibility for users who are able to accept the risks.", false);

ConfigKey<String> SAMLUserSessionKeyPathAttribute = new ConfigKey<String>("Advanced", String.class, "saml2.user.sessionkey.path", "",
"The Path attribute of sessionkey cookie when SAML users have logged in. If not set, it will be set to the path of SAML redirection URL (saml2.redirect.url).", true);

SAMLProviderMetadata getSPMetadata();
SAMLProviderMetadata getIdPMetadata(String entityId);
Collection<SAMLProviderMetadata> getAllIdPMetadata();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ public ConfigKey<?>[] getConfigKeys() {
SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL,
SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature};
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature,
SAMLUserSessionKeyPathAttribute};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
Expand Down Expand Up @@ -102,7 +104,9 @@
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import com.cloud.api.ApiServlet;
import com.cloud.utils.HttpUtils;
import com.cloud.utils.exception.CloudRuntimeException;

public class SAMLUtils {
protected static Logger LOGGER = LogManager.getLogger(SAMLUtils.class);
Expand Down Expand Up @@ -297,7 +301,26 @@ public static void setupSamlUserCookies(final LoginCmdResponse loginResponse, fi
resp.addCookie(new Cookie("timezone", URLEncoder.encode(timezone, HttpUtils.UTF_8)));
}
resp.addCookie(new Cookie("userfullname", URLEncoder.encode(loginResponse.getFirstName() + " " + loginResponse.getLastName(), HttpUtils.UTF_8).replace("+", "%20")));
resp.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly;Path=/client/api", ApiConstants.SESSIONKEY, loginResponse.getSessionKey()));

String redirectUrl = SAML2AuthManager.SAMLCloudStackRedirectionUrl.value();
String path = SAML2AuthManager.SAMLUserSessionKeyPathAttribute.value();
String domain = null;
try {
URI redirectUri = new URI(redirectUrl);
domain = redirectUri.getHost();
if (StringUtils.isBlank(path)) {
path = redirectUri.getPath();
}
if (StringUtils.isBlank(path)) {
path = "/";
}
} catch (URISyntaxException ex) {
throw new CloudRuntimeException("Invalid URI: " + redirectUrl);
}
String sameSite = ApiServlet.getApiSessionKeySameSite();
String sessionKeyCookie = String.format("%s=%s;Domain=%s;Path=%s;%s", ApiConstants.SESSIONKEY, loginResponse.getSessionKey(), domain, path, sameSite);
LOGGER.debug("Adding sessionkey cookie to response: " + sessionKeyCookie);
resp.addHeader("SET-COOKIE", sessionKeyCookie);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
Expand Down Expand Up @@ -88,13 +89,17 @@ public class ListAndSwitchSAMLAccountCmdTest extends TestCase {
@Mock
HttpServletRequest req;

final String sessionId = "node0xxxxxxxxxxxxx";
Cookie[] cookies;

@Test
public void testListAndSwitchSAMLAccountCmd() throws Exception {
// Setup
final Map<String, Object[]> params = new HashMap<String, Object[]>();
final String sessionKeyValue = "someSessionIDValue";
Mockito.when(session.getAttribute(ApiConstants.SESSIONKEY)).thenReturn(sessionKeyValue);
Mockito.when(session.getAttribute("userid")).thenReturn(2L);
Mockito.when(session.getId()).thenReturn(sessionId);
params.put(ApiConstants.USER_ID, new String[]{"2"});
params.put(ApiConstants.DOMAIN_ID, new String[]{"1"});
Mockito.when(userDao.findByUuid(anyString())).thenReturn(new UserVO(2L));
Expand Down Expand Up @@ -146,7 +151,25 @@ public void testListAndSwitchSAMLAccountCmd() throws Exception {
Mockito.verify(accountService, Mockito.times(0)).getUserAccountById(Mockito.anyLong());
}

// valid sessionkey value test
// valid sessionkey value and invalid JSESSIONID test
cookies = new Cookie[2];
cookies[0] = new Cookie(ApiConstants.SESSIONKEY, sessionKeyValue);
cookies[1] = new Cookie("JSESSIONID", "invalid-JSESSIONID");
Mockito.when(req.getCookies()).thenReturn(cookies);
params.put(ApiConstants.SESSIONKEY, new String[]{sessionKeyValue});
try {
cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
} catch (ServerApiException exception) {
assertEquals(exception.getErrorCode(), ApiErrorCode.UNAUTHORIZED);
} finally {
Mockito.verify(accountService, Mockito.times(0)).getUserAccountById(Mockito.anyLong());
}

// valid sessionkey value and valid JSESSIONID test
cookies = new Cookie[2];
cookies[0] = new Cookie(ApiConstants.SESSIONKEY, sessionKeyValue);
cookies[1] = new Cookie("JSESSIONID", sessionId + ".node0");
Mockito.when(req.getCookies()).thenReturn(cookies);
params.put(ApiConstants.SESSIONKEY, new String[]{sessionKeyValue});
try {
cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
Expand Down
Loading

0 comments on commit 57e54ce

Please sign in to comment.