mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
CLOUDSTACK-10103: Cloudian Connector for CloudStack (#2284)
Several organizations use Cloudian as S3 provider, this implements the Cloudian Management Console connector for CloudStack that can do the following: - Provide ease in connector configuration using CloudStack global settings - Perform SSO from CloudStack UI into Cloudian Management Console (CMC) when the connector is enabled - Automatic provisioning and de-provisioning of CloudStack accounts and domains as Cloudian users and groups respectively - During CloudStack UI logout, logout user from CMC - CloudStack account will be mapped to Cloudian Users, and CloudStack domain will be mapped to Cloudian Groups. - The CloudStack admin account is mapped to Cloudian admin (user name configurable). - The user/group provisioning will be from CloudStack to Cloudian only, i.e. user/group addition/removal/updation/deactivation in Cloudian portal (CMC) won't propagate the changes to CloudStack. FS: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Cloudian+Connector+for+CloudStack New APIs: - `cloudianIsEnabled`: API to check whether Cloudian Connector is enabled. - `cloudianSsoLogin`: Performs SSO for the logged-in, requesting user and returns the URL that can be used to perform SSO and log into CMC. New Global Settings: - cloudian.connector.enabled (false) If set to true, this enables the Cloudian Connector for CloudStack. Restarting management server(s) is required. - cloudian.admin.host (s3-admin.cloudian.com) The host where Cloudian Admin services are accessible. - cloudian.admin.port (19443) The admin service port. - cloudian.admin.protocol (https) The admin service API scheme/protocol. - cloudian.validate.ssl (true) When set to true, this validates the certificate of the https-enabled admin API service. - cloudian.admin.user (sysadmin) The admin user's name when making (admin) API calls. - cloudian.admin.password (public) The admin password used when making (admin) API calls. - cloudian.api.request.timeout (5) The API request timeout in seconds used by the internal HTTP/s client. - cloudian.cmc.admin.user (admin) The CMC admin user's name. - cloudian.cmc.host (cmc.cloudian.com) The CMC host. - cloudian.cmc.port (8443) The CMC service port. - cloudian.cmc.protocol (https) The CMC service scheme/protocol. - cloudian.sso.key (ss0sh5r3dk3y) The Single-Sign-On shared key. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
bd953d811f
commit
b6dc40faef
@ -414,6 +414,11 @@
|
|||||||
<artifactId>cloud-plugin-database-quota</artifactId>
|
<artifactId>cloud-plugin-database-quota</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.cloudstack</groupId>
|
||||||
|
<artifactId>cloud-plugin-integrations-cloudian-connector</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.cloudstack</groupId>
|
<groupId>org.apache.cloudstack</groupId>
|
||||||
<artifactId>cloud-plugin-integrations-prometheus-exporter</artifactId>
|
<artifactId>cloud-plugin-integrations-prometheus-exporter</artifactId>
|
||||||
|
|||||||
60
plugins/integrations/cloudian/pom.xml
Normal file
60
plugins/integrations/cloudian/pom.xml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
|
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>cloud-plugin-integrations-cloudian-connector</artifactId>
|
||||||
|
<name>Apache CloudStack Plugin - Cloudian Connector</name>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.apache.cloudstack</groupId>
|
||||||
|
<artifactId>cloudstack-plugins</artifactId>
|
||||||
|
<version>4.11.0.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.cloudstack</groupId>
|
||||||
|
<artifactId>cloud-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.cloudstack</groupId>
|
||||||
|
<artifactId>cloud-utils</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>${cs.httpclient.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${cs.jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.tomakehurst</groupId>
|
||||||
|
<artifactId>wiremock</artifactId>
|
||||||
|
<version>${cs.wiremock.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
name=cloudian
|
||||||
|
parent=api
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
or more contributor license agreements. See the NOTICE file
|
||||||
|
distributed with this work for additional information
|
||||||
|
regarding copyright ownership. The ASF licenses this file
|
||||||
|
to you under the Apache License, Version 2.0 (the
|
||||||
|
"License"); you may not use this file except in compliance
|
||||||
|
with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing,
|
||||||
|
software distributed under the License is distributed on an
|
||||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
KIND, either express or implied. See the License for the
|
||||||
|
specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
-->
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||||
|
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||||
|
<bean id="cloudianConnector" class="org.apache.cloudstack.cloudian.CloudianConnectorImpl" >
|
||||||
|
</bean>
|
||||||
|
</beans>
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
|
|
||||||
|
import com.cloud.utils.component.PluggableService;
|
||||||
|
|
||||||
|
public interface CloudianConnector extends PluggableService {
|
||||||
|
|
||||||
|
ConfigKey<Boolean> CloudianConnectorEnabled = new ConfigKey<>("Advanced", Boolean.class, "cloudian.connector.enabled", "false",
|
||||||
|
"If set to true, this enables the Cloudian Connector for CloudStack.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianAdminHost = new ConfigKey<>("Advanced", String.class, "cloudian.admin.host", "s3-admin.cloudian.com",
|
||||||
|
"The hostname of the Cloudian Admin server.", true);
|
||||||
|
|
||||||
|
ConfigKey<Integer> CloudianAdminPort = new ConfigKey<>("Advanced", Integer.class, "cloudian.admin.port", "19443",
|
||||||
|
"The port of the Cloudian Admin server.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianAdminProtocol = new ConfigKey<>("Advanced", String.class, "cloudian.admin.protocol", "https",
|
||||||
|
"The protocol of the Cloudian Admin server.", true);
|
||||||
|
|
||||||
|
ConfigKey<Boolean> CloudianValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "cloudian.validate.ssl", "true",
|
||||||
|
"When set to true, this will validate the SSL certificate when connecting to https/ssl enabled admin host.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianAdminUser = new ConfigKey<>("Advanced", String.class, "cloudian.admin.user", "sysadmin",
|
||||||
|
"The system admin user for accessing the Cloudian Admin server.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianAdminPassword = new ConfigKey<>("Advanced", String.class, "cloudian.admin.password", "public",
|
||||||
|
"The system admin password for the Cloudian Admin server.", true);
|
||||||
|
|
||||||
|
ConfigKey<Integer> CloudianAdminApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "cloudian.api.request.timeout", "5",
|
||||||
|
"The admin API request timeout in seconds.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianCmcAdminUser = new ConfigKey<>("Advanced", String.class, "cloudian.cmc.admin.user", "admin",
|
||||||
|
"The admin user name for accessing the Cloudian Management Console.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianCmcHost = new ConfigKey<>("Advanced", String.class, "cloudian.cmc.host", "cmc.cloudian.com",
|
||||||
|
"The hostname of the Cloudian Management Console.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianCmcPort = new ConfigKey<>("Advanced", String.class, "cloudian.cmc.port", "8443",
|
||||||
|
"The port of the Cloudian Management Console.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianCmcProtocol = new ConfigKey<>("Advanced", String.class, "cloudian.cmc.protocol", "https",
|
||||||
|
"The protocol of the Cloudian Management Console.", true);
|
||||||
|
|
||||||
|
ConfigKey<String> CloudianSsoKey = new ConfigKey<>("Advanced", String.class, "cloudian.sso.key", "ss0sh5r3dk3y",
|
||||||
|
"The shared single sign-on key as configured in Cloudian CMC.", true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the base Cloudian Management Console URL
|
||||||
|
* @return returns the url string
|
||||||
|
*/
|
||||||
|
String getCmcUrl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Cloudian Connector is enabled
|
||||||
|
* @return returns true is connector is enabled
|
||||||
|
*/
|
||||||
|
boolean isEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates single-sign on URL for logged in user
|
||||||
|
* @return returns the SSO URL string
|
||||||
|
*/
|
||||||
|
String generateSsoUrl();
|
||||||
|
}
|
||||||
@ -0,0 +1,345 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian;
|
||||||
|
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.naming.ConfigurationException;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
|
import org.apache.cloudstack.api.ApiErrorCode;
|
||||||
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.cloudian.api.CloudianIsEnabledCmd;
|
||||||
|
import org.apache.cloudstack.cloudian.api.CloudianSsoLoginCmd;
|
||||||
|
import org.apache.cloudstack.cloudian.client.CloudianClient;
|
||||||
|
import org.apache.cloudstack.cloudian.client.CloudianGroup;
|
||||||
|
import org.apache.cloudstack.cloudian.client.CloudianUser;
|
||||||
|
import org.apache.cloudstack.cloudian.client.CloudianUtils;
|
||||||
|
import org.apache.cloudstack.context.CallContext;
|
||||||
|
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||||
|
import org.apache.cloudstack.framework.config.Configurable;
|
||||||
|
import org.apache.cloudstack.framework.messagebus.MessageBus;
|
||||||
|
import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import com.cloud.domain.Domain;
|
||||||
|
import com.cloud.domain.DomainVO;
|
||||||
|
import com.cloud.domain.dao.DomainDao;
|
||||||
|
import com.cloud.user.Account;
|
||||||
|
import com.cloud.user.AccountManager;
|
||||||
|
import com.cloud.user.DomainManager;
|
||||||
|
import com.cloud.user.User;
|
||||||
|
import com.cloud.user.dao.AccountDao;
|
||||||
|
import com.cloud.user.dao.UserDao;
|
||||||
|
import com.cloud.utils.component.ComponentLifecycleBase;
|
||||||
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
|
||||||
|
public class CloudianConnectorImpl extends ComponentLifecycleBase implements CloudianConnector, Configurable {
|
||||||
|
private static final Logger LOG = Logger.getLogger(CloudianConnectorImpl.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private UserDao userDao;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private AccountDao accountDao;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private DomainDao domainDao;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private MessageBus messageBus;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
//////////////// Plugin Methods /////////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
private CloudianClient getClient() {
|
||||||
|
try {
|
||||||
|
return new CloudianClient(CloudianAdminHost.value(), CloudianAdminPort.value(), CloudianAdminProtocol.value(),
|
||||||
|
CloudianAdminUser.value(), CloudianAdminPassword.value(),
|
||||||
|
CloudianValidateSSLSecurity.value(), CloudianAdminApiRequestTimeout.value());
|
||||||
|
} catch (final KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
|
LOG.error("Failed to create Cloudian API client due to: ", e);
|
||||||
|
}
|
||||||
|
throw new CloudRuntimeException("Failed to create and return Cloudian API client instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addGroup(final Domain domain) {
|
||||||
|
if (domain == null || !isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CloudianClient client = getClient();
|
||||||
|
final CloudianGroup group = new CloudianGroup();
|
||||||
|
group.setGroupId(domain.getUuid());
|
||||||
|
group.setGroupName(domain.getPath());
|
||||||
|
group.setActive(true);
|
||||||
|
return client.addGroup(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean removeGroup(final Domain domain) {
|
||||||
|
if (domain == null || !isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CloudianClient client = getClient();
|
||||||
|
for (final CloudianUser user: client.listUsers(domain.getUuid())) {
|
||||||
|
if (client.removeUser(user.getUserId(), domain.getUuid())) {
|
||||||
|
LOG.error(String.format("Failed to remove Cloudian user id=%s, while removing Cloudian group id=%s", user.getUserId(), domain.getUuid()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int retry = 0; retry < 3; retry++) {
|
||||||
|
if (client.removeGroup(domain.getUuid())) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG.warn("Failed to remove Cloudian group id=" + domain.getUuid() + ", retrying count=" + retry+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.warn("Failed to remove Cloudian group id=" + domain.getUuid() + ", please remove manually");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addUserAccount(final Account account, final Domain domain) {
|
||||||
|
if (account == null || domain == null || !isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final User accountUser = userDao.listByAccount(account.getId()).get(0);
|
||||||
|
final CloudianClient client = getClient();
|
||||||
|
final String fullName = String.format("%s %s (%s)", accountUser.getFirstname(), accountUser.getLastname(), account.getAccountName());
|
||||||
|
final CloudianUser user = new CloudianUser();
|
||||||
|
user.setUserId(account.getUuid());
|
||||||
|
user.setGroupId(domain.getUuid());
|
||||||
|
user.setFullName(fullName);
|
||||||
|
user.setEmailAddr(accountUser.getEmail());
|
||||||
|
user.setUserType(CloudianUser.USER);
|
||||||
|
user.setActive(true);
|
||||||
|
return client.addUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean updateUserAccount(final Account account, final Domain domain, final CloudianUser existingUser) {
|
||||||
|
if (account == null || domain == null || !isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CloudianClient client = getClient();
|
||||||
|
if (existingUser != null) {
|
||||||
|
final User accountUser = userDao.listByAccount(account.getId()).get(0);
|
||||||
|
final String fullName = String.format("%s %s (%s)", accountUser.getFirstname(), accountUser.getLastname(), account.getAccountName());
|
||||||
|
if (!existingUser.getActive() || !existingUser.getFullName().equals(fullName) || !existingUser.getEmailAddr().equals(accountUser.getEmail())) {
|
||||||
|
existingUser.setActive(true);
|
||||||
|
existingUser.setFullName(fullName);
|
||||||
|
existingUser.setEmailAddr(accountUser.getEmail());
|
||||||
|
return client.updateUser(existingUser);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean removeUserAccount(final Account account) {
|
||||||
|
if (account == null || !isEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CloudianClient client = getClient();
|
||||||
|
final Domain domain = domainDao.findById(account.getDomainId());
|
||||||
|
for (int retry = 0; retry < 3; retry++) {
|
||||||
|
if (client.removeUser(account.getUuid(), domain.getUuid())) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
LOG.warn("Failed to remove Cloudian user id=" + account.getUuid() + " in group id=" + domain.getUuid() + ", retrying count=" + retry+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.warn("Failed to remove Cloudian user id=" + account.getUuid() + " in group id=" + domain.getUuid() + ", please remove manually");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
//////////////// Plugin APIs /////////////////////
|
||||||
|
//////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCmcUrl() {
|
||||||
|
return String.format("%s://%s:%s/Cloudian/", CloudianCmcProtocol.value(),
|
||||||
|
CloudianCmcHost.value(), CloudianCmcPort.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return CloudianConnectorEnabled.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateSsoUrl() {
|
||||||
|
final Account caller = CallContext.current().getCallingAccount();
|
||||||
|
final Domain domain = domainDao.findById(caller.getDomainId());
|
||||||
|
|
||||||
|
String user = caller.getUuid();
|
||||||
|
String group = domain.getUuid();
|
||||||
|
|
||||||
|
if (caller.getAccountName().equals("admin") && caller.getRoleId() == RoleType.Admin.getId()) {
|
||||||
|
user = CloudianCmcAdminUser.value();
|
||||||
|
group = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug(String.format("Attempting Cloudian SSO with user id=%s, group id=%s", user, group));
|
||||||
|
|
||||||
|
final CloudianUser ssoUser = getClient().listUser(user, group);
|
||||||
|
if (ssoUser == null || !ssoUser.getActive()) {
|
||||||
|
LOG.debug(String.format("Failed to find existing Cloudian user id=%s in group id=%s", user, group));
|
||||||
|
final CloudianGroup ssoGroup = getClient().listGroup(group);
|
||||||
|
if (ssoGroup == null) {
|
||||||
|
LOG.debug(String.format("Failed to find existing Cloudian group id=%s, trying to add it", group));
|
||||||
|
if (!addGroup(domain)) {
|
||||||
|
LOG.error("Failed to add missing Cloudian group id=" + group);
|
||||||
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Aborting Cloudian SSO, failed to add group to Cloudian.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!addUserAccount(caller, domain)) {
|
||||||
|
LOG.error("Failed to add missing Cloudian group id=" + group);
|
||||||
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Aborting Cloudian SSO, failed to add user to Cloudian.");
|
||||||
|
}
|
||||||
|
final CloudianUser addedSsoUser = getClient().listUser(user, group);
|
||||||
|
if (addedSsoUser == null || !addedSsoUser.getActive()) {
|
||||||
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Aborting Cloudian SSO, failed to find mapped Cloudian user, please fix integration issues.");
|
||||||
|
}
|
||||||
|
} else if (!group.equals("0")) {
|
||||||
|
updateUserAccount(caller, domain, ssoUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug(String.format("Validated Cloudian SSO for Cloudian user id=%s, group id=%s", user, group));
|
||||||
|
return CloudianUtils.generateSSOUrl(getCmcUrl(), user, group, CloudianSsoKey.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
//////////////// Plugin Configuration /////////////////////
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||||
|
super.configure(name, params);
|
||||||
|
|
||||||
|
if (!isEnabled()) {
|
||||||
|
LOG.debug("Cloudian connector is disabled, skipping configuration");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug(String.format("Cloudian connector is enabled, completed configuration, integration is ready. " +
|
||||||
|
"Cloudian admin host:%s, port:%s, user:%s",
|
||||||
|
CloudianAdminHost.value(), CloudianAdminPort.value(), CloudianAdminUser.value()));
|
||||||
|
|
||||||
|
messageBus.subscribe(AccountManager.MESSAGE_ADD_ACCOUNT_EVENT, new MessageSubscriber() {
|
||||||
|
@Override
|
||||||
|
public void onPublishMessage(String senderAddress, String subject, Object args) {
|
||||||
|
try {
|
||||||
|
final Map<Long, Long> accountGroupMap = (Map<Long, Long>) args;
|
||||||
|
final Long accountId = accountGroupMap.keySet().iterator().next();
|
||||||
|
final Account account = accountDao.findById(accountId);
|
||||||
|
final Domain domain = domainDao.findById(account.getDomainId());
|
||||||
|
|
||||||
|
if (!addUserAccount(account, domain)) {
|
||||||
|
LOG.warn(String.format("Failed to add account in Cloudian while adding CloudStack account=%s in domain=%s", account.getAccountName(), domain.getPath()));
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Caught exception while adding account in Cloudian: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
messageBus.subscribe(AccountManager.MESSAGE_REMOVE_ACCOUNT_EVENT, new MessageSubscriber() {
|
||||||
|
@Override
|
||||||
|
public void onPublishMessage(String senderAddress, String subject, Object args) {
|
||||||
|
try {
|
||||||
|
final Account account = accountDao.findByIdIncludingRemoved((Long) args);
|
||||||
|
if(!removeUserAccount(account)) {
|
||||||
|
LOG.warn(String.format("Failed to remove account to Cloudian while removing CloudStack account=%s, id=%s", account.getAccountName(), account.getId()));
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Caught exception while removing account in Cloudian: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
messageBus.subscribe(DomainManager.MESSAGE_ADD_DOMAIN_EVENT, new MessageSubscriber() {
|
||||||
|
@Override
|
||||||
|
public void onPublishMessage(String senderAddress, String subject, Object args) {
|
||||||
|
try {
|
||||||
|
final Domain domain = domainDao.findById((Long) args);
|
||||||
|
if (!addGroup(domain)) {
|
||||||
|
LOG.warn(String.format("Failed to add group in Cloudian while adding CloudStack domain=%s id=%s", domain.getPath(), domain.getId()));
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Caught exception adding domain/group in Cloudian: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
messageBus.subscribe(DomainManager.MESSAGE_REMOVE_DOMAIN_EVENT, new MessageSubscriber() {
|
||||||
|
@Override
|
||||||
|
public void onPublishMessage(String senderAddress, String subject, Object args) {
|
||||||
|
try {
|
||||||
|
final DomainVO domain = (DomainVO) args;
|
||||||
|
if (!removeGroup(domain)) {
|
||||||
|
LOG.warn(String.format("Failed to remove group in Cloudian while removing CloudStack domain=%s id=%s", domain.getPath(), domain.getId()));
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Caught exception while removing domain/group in Cloudian: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Class<?>> getCommands() {
|
||||||
|
final List<Class<?>> cmdList = new ArrayList<Class<?>>();
|
||||||
|
cmdList.add(CloudianIsEnabledCmd.class);
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return cmdList;
|
||||||
|
}
|
||||||
|
cmdList.add(CloudianSsoLoginCmd.class);
|
||||||
|
return cmdList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigComponentName() {
|
||||||
|
return CloudianConnector.class.getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigKey<?>[] getConfigKeys() {
|
||||||
|
return new ConfigKey<?>[] {
|
||||||
|
CloudianConnectorEnabled,
|
||||||
|
CloudianAdminHost,
|
||||||
|
CloudianAdminPort,
|
||||||
|
CloudianAdminUser,
|
||||||
|
CloudianAdminPassword,
|
||||||
|
CloudianAdminProtocol,
|
||||||
|
CloudianAdminApiRequestTimeout,
|
||||||
|
CloudianValidateSSLSecurity,
|
||||||
|
CloudianCmcAdminUser,
|
||||||
|
CloudianCmcHost,
|
||||||
|
CloudianCmcPort,
|
||||||
|
CloudianCmcProtocol,
|
||||||
|
CloudianSsoKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.api;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
|
import org.apache.cloudstack.api.APICommand;
|
||||||
|
import org.apache.cloudstack.api.BaseCmd;
|
||||||
|
import org.apache.cloudstack.cloudian.CloudianConnector;
|
||||||
|
import org.apache.cloudstack.cloudian.response.CloudianEnabledResponse;
|
||||||
|
|
||||||
|
import com.cloud.user.Account;
|
||||||
|
|
||||||
|
@APICommand(name = CloudianIsEnabledCmd.APINAME, description = "Checks if the Cloudian Connector is enabled",
|
||||||
|
responseObject = CloudianEnabledResponse.class,
|
||||||
|
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||||
|
since = "4.11.0",
|
||||||
|
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||||
|
public class CloudianIsEnabledCmd extends BaseCmd {
|
||||||
|
public static final String APINAME = "cloudianIsEnabled";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CloudianConnector connector;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////// API Implementation///////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandName() {
|
||||||
|
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getEntityOwnerId() {
|
||||||
|
return Account.ACCOUNT_ID_SYSTEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
final CloudianEnabledResponse response = new CloudianEnabledResponse();
|
||||||
|
response.setEnabled(connector.isEnabled());
|
||||||
|
response.setCmcUrl(connector.getCmcUrl());
|
||||||
|
response.setObjectName(APINAME.toLowerCase());
|
||||||
|
response.setResponseName(getCommandName());
|
||||||
|
setResponseObject(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.api;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.acl.RoleType;
|
||||||
|
import org.apache.cloudstack.api.APICommand;
|
||||||
|
import org.apache.cloudstack.api.ApiErrorCode;
|
||||||
|
import org.apache.cloudstack.api.BaseCmd;
|
||||||
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.cloudian.CloudianConnector;
|
||||||
|
import org.apache.cloudstack.cloudian.response.CloudianSsoLoginResponse;
|
||||||
|
|
||||||
|
import com.cloud.user.Account;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
@APICommand(name = CloudianSsoLoginCmd.APINAME, description = "Generates single-sign-on login url for logged-in CloudStack user to access the Cloudian Management Console",
|
||||||
|
responseObject = CloudianSsoLoginResponse.class,
|
||||||
|
since = "4.11.0",
|
||||||
|
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||||
|
public class CloudianSsoLoginCmd extends BaseCmd {
|
||||||
|
public static final String APINAME = "cloudianSsoLogin";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CloudianConnector connector;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
/////////////// API Implementation///////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandName() {
|
||||||
|
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getEntityOwnerId() {
|
||||||
|
return Account.ACCOUNT_ID_SYSTEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
final String ssoUrl = connector.generateSsoUrl();
|
||||||
|
if (Strings.isNullOrEmpty(ssoUrl)) {
|
||||||
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to generate Cloudian single-sign on URL for the user");
|
||||||
|
}
|
||||||
|
final CloudianSsoLoginResponse response = new CloudianSsoLoginResponse();
|
||||||
|
response.setSsoRedirectUrl(ssoUrl);
|
||||||
|
response.setResponseName(getCommandName());
|
||||||
|
response.setObjectName(APINAME.toLowerCase());
|
||||||
|
setResponseObject(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,347 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.ApiErrorCode;
|
||||||
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.utils.security.SSLUtils;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.http.auth.AuthScope;
|
||||||
|
import org.apache.http.auth.Credentials;
|
||||||
|
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||||
|
import org.apache.http.client.AuthCache;
|
||||||
|
import org.apache.http.client.CredentialsProvider;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpPut;
|
||||||
|
import org.apache.http.client.protocol.HttpClientContext;
|
||||||
|
import org.apache.http.conn.ConnectTimeoutException;
|
||||||
|
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.auth.BasicScheme;
|
||||||
|
import org.apache.http.impl.client.BasicAuthCache;
|
||||||
|
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import com.cloud.utils.nio.TrustAllManager;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
public class CloudianClient {
|
||||||
|
private static final Logger LOG = Logger.getLogger(CloudianClient.class);
|
||||||
|
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private final HttpClientContext httpContext;
|
||||||
|
private final String adminApiUrl;
|
||||||
|
|
||||||
|
public CloudianClient(final String host, final Integer port, final String scheme, final String username, final String password, final boolean validateSSlCertificate, final int timeout) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
|
||||||
|
final CredentialsProvider provider = new BasicCredentialsProvider();
|
||||||
|
provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
|
||||||
|
final HttpHost adminHost = new HttpHost(host, port, scheme);
|
||||||
|
final AuthCache authCache = new BasicAuthCache();
|
||||||
|
authCache.put(adminHost, new BasicScheme());
|
||||||
|
|
||||||
|
this.adminApiUrl = adminHost.toURI();
|
||||||
|
this.httpContext = HttpClientContext.create();
|
||||||
|
this.httpContext.setCredentialsProvider(provider);
|
||||||
|
this.httpContext.setAuthCache(authCache);
|
||||||
|
|
||||||
|
final RequestConfig config = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(timeout * 1000)
|
||||||
|
.setConnectionRequestTimeout(timeout * 1000)
|
||||||
|
.setSocketTimeout(timeout * 1000)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if (!validateSSlCertificate) {
|
||||||
|
final SSLContext sslcontext = SSLUtils.getSSLContext();
|
||||||
|
sslcontext.init(null, new X509TrustManager[]{new TrustAllManager()}, new SecureRandom());
|
||||||
|
final SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE);
|
||||||
|
this.httpClient = HttpClientBuilder.create()
|
||||||
|
.setDefaultCredentialsProvider(provider)
|
||||||
|
.setDefaultRequestConfig(config)
|
||||||
|
.setSSLSocketFactory(factory)
|
||||||
|
.build();
|
||||||
|
} else {
|
||||||
|
this.httpClient = HttpClientBuilder.create()
|
||||||
|
.setDefaultCredentialsProvider(provider)
|
||||||
|
.setDefaultRequestConfig(config)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAuthFailure(final HttpResponse response) {
|
||||||
|
if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
|
||||||
|
final Credentials credentials = httpContext.getCredentialsProvider().getCredentials(AuthScope.ANY);
|
||||||
|
LOG.error("Cloudian admin API authentication failed, please check Cloudian configuration. Admin auth principal=" + credentials.getUserPrincipal() + ", password=" + credentials.getPassword() + ", API url=" + adminApiUrl);
|
||||||
|
throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, "Cloudian backend API call unauthorized, please ask your administrator to fix integration issues.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkResponseOK(final HttpResponse response) {
|
||||||
|
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) {
|
||||||
|
LOG.debug("Requested Cloudian resource does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK && response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) {
|
||||||
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to find the requested resource and get valid response from Cloudian backend API call, please ask your administrator to diagnose and fix issues.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkEmptyResponse(final HttpResponse response) throws IOException {
|
||||||
|
return response != null && (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT ||
|
||||||
|
response.getEntity() == null ||
|
||||||
|
response.getEntity().getContent() == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkResponseTimeOut(final Exception e) {
|
||||||
|
if (e instanceof ConnectTimeoutException || e instanceof SocketTimeoutException) {
|
||||||
|
throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, "Operation timed out, please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponse delete(final String path) throws IOException {
|
||||||
|
final HttpResponse response = httpClient.execute(new HttpDelete(adminApiUrl + path), httpContext);
|
||||||
|
checkAuthFailure(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponse get(final String path) throws IOException {
|
||||||
|
final HttpResponse response = httpClient.execute(new HttpGet(adminApiUrl + path), httpContext);
|
||||||
|
checkAuthFailure(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponse post(final String path, final Object item) throws IOException {
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
final String json = mapper.writeValueAsString(item);
|
||||||
|
final StringEntity entity = new StringEntity(json);
|
||||||
|
final HttpPost request = new HttpPost(adminApiUrl + path);
|
||||||
|
request.setHeader("Content-type", "application/json");
|
||||||
|
request.setEntity(entity);
|
||||||
|
final HttpResponse response = httpClient.execute(request, httpContext);
|
||||||
|
checkAuthFailure(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpResponse put(final String path, final Object item) throws IOException {
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
final String json = mapper.writeValueAsString(item);
|
||||||
|
final StringEntity entity = new StringEntity(json);
|
||||||
|
final HttpPut request = new HttpPut(adminApiUrl + path);
|
||||||
|
request.setHeader("Content-type", "application/json");
|
||||||
|
request.setEntity(entity);
|
||||||
|
final HttpResponse response = httpClient.execute(request, httpContext);
|
||||||
|
checkAuthFailure(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
//////////////// Public APIs: User /////////////////////
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public boolean addUser(final CloudianUser user) {
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.debug("Adding Cloudian user: " + user);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = put("/user", user);
|
||||||
|
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to add Cloudian user due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloudianUser listUser(final String userId, final String groupId) {
|
||||||
|
if (Strings.isNullOrEmpty(userId) || Strings.isNullOrEmpty(groupId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LOG.debug("Trying to find Cloudian user with id=" + userId + " and group id=" + groupId);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = get(String.format("/user?userId=%s&groupId=%s", userId, groupId));
|
||||||
|
checkResponseOK(response);
|
||||||
|
if (checkEmptyResponse(response)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
return mapper.readValue(response.getEntity().getContent(), CloudianUser.class);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to list Cloudian user due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CloudianUser> listUsers(final String groupId) {
|
||||||
|
if (Strings.isNullOrEmpty(groupId)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
LOG.debug("Trying to list Cloudian users in group id=" + groupId);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = get(String.format("/user/list?groupId=%s&userType=all&userStatus=active", groupId));
|
||||||
|
checkResponseOK(response);
|
||||||
|
if (checkEmptyResponse(response)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
return Arrays.asList(mapper.readValue(response.getEntity().getContent(), CloudianUser[].class));
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to list Cloudian users due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean updateUser(final CloudianUser user) {
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.debug("Updating Cloudian user: " + user);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = post("/user", user);
|
||||||
|
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to update Cloudian user due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeUser(final String userId, final String groupId) {
|
||||||
|
if (Strings.isNullOrEmpty(userId) || Strings.isNullOrEmpty(groupId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.debug("Removing Cloudian user with user id=" + userId + " in group id=" + groupId);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = delete(String.format("/user?userId=%s&groupId=%s", userId, groupId));
|
||||||
|
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to remove Cloudian user due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
//////////////// Public APIs: Group /////////////////////
|
||||||
|
/////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public boolean addGroup(final CloudianGroup group) {
|
||||||
|
if (group == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.debug("Adding Cloudian group: " + group);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = put("/group", group);
|
||||||
|
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to add Cloudian group due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloudianGroup listGroup(final String groupId) {
|
||||||
|
if (Strings.isNullOrEmpty(groupId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LOG.debug("Trying to find Cloudian group with id=" + groupId);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = get(String.format("/group?groupId=%s", groupId));
|
||||||
|
checkResponseOK(response);
|
||||||
|
if (checkEmptyResponse(response)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
return mapper.readValue(response.getEntity().getContent(), CloudianGroup.class);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to list Cloudian group due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CloudianGroup> listGroups() {
|
||||||
|
LOG.debug("Trying to list Cloudian groups");
|
||||||
|
try {
|
||||||
|
final HttpResponse response = get("/group/list");
|
||||||
|
checkResponseOK(response);
|
||||||
|
if (checkEmptyResponse(response)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
return Arrays.asList(mapper.readValue(response.getEntity().getContent(), CloudianGroup[].class));
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to list Cloudian groups due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean updateGroup(final CloudianGroup group) {
|
||||||
|
if (group == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.debug("Updating Cloudian group: " + group);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = post("/group", group);
|
||||||
|
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to remove group due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeGroup(final String groupId) {
|
||||||
|
if (Strings.isNullOrEmpty(groupId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LOG.debug("Removing Cloudian group id=" + groupId);
|
||||||
|
try {
|
||||||
|
final HttpResponse response = delete(String.format("/group?groupId=%s", groupId));
|
||||||
|
return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
LOG.error("Failed to remove group due to:", e);
|
||||||
|
checkResponseTimeOut(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.client;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class CloudianGroup {
|
||||||
|
String groupId;
|
||||||
|
String groupName;
|
||||||
|
Boolean active;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Cloudian Group [id=%s, name=%s, active=%s]", groupId, groupName, active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupName() {
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupName(String groupName) {
|
||||||
|
this.groupName = groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(Boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.client;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class CloudianUser {
|
||||||
|
public static final String USER = "User";
|
||||||
|
|
||||||
|
String userId;
|
||||||
|
String groupId;
|
||||||
|
String userType;
|
||||||
|
String fullName;
|
||||||
|
String emailAddr;
|
||||||
|
Boolean active;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Cloudian User [id=%s, group id=%s, type=%s, active=%s, name=%s]", userId, groupId, userType, active, fullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserType() {
|
||||||
|
return userType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserType(String userType) {
|
||||||
|
this.userType = userType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullName(String fullName) {
|
||||||
|
this.fullName = fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmailAddr() {
|
||||||
|
return emailAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailAddr(String emailAddr) {
|
||||||
|
this.emailAddr = emailAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(Boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.client;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
import com.cloud.utils.HttpUtils;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
public class CloudianUtils {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(CloudianUtils.class);
|
||||||
|
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates RFC-2104 compliant HMAC signature
|
||||||
|
* @param data
|
||||||
|
* @param key
|
||||||
|
* @return returns the generated signature or null on error
|
||||||
|
*/
|
||||||
|
public static String generateHMACSignature(final String data, final String key) {
|
||||||
|
if (Strings. isNullOrEmpty(data) || Strings.isNullOrEmpty(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
|
||||||
|
final Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
|
||||||
|
mac.init(signingKey);
|
||||||
|
byte[] rawHmac = mac.doFinal(data.getBytes());
|
||||||
|
return Base64.encodeBase64String(rawHmac);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
LOG.error("Failed to generate HMAC signature from provided data and key, due to: ", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates URL parameters for single-sign on URL
|
||||||
|
* @param user
|
||||||
|
* @param group
|
||||||
|
* @param ssoKey
|
||||||
|
* @return returns SSO URL parameters or null on error
|
||||||
|
*/
|
||||||
|
public static String generateSSOUrl(final String cmcUrlPath, final String user, final String group, final String ssoKey) {
|
||||||
|
final StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.append("user=").append(user);
|
||||||
|
stringBuilder.append("&group=").append(group);
|
||||||
|
stringBuilder.append("×tamp=").append(System.currentTimeMillis());
|
||||||
|
|
||||||
|
final String signature = generateHMACSignature(stringBuilder.toString(), ssoKey);
|
||||||
|
if (Strings.isNullOrEmpty(signature)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
stringBuilder.append("&signature=").append(URLEncoder.encode(signature, HttpUtils.UTF_8));
|
||||||
|
} catch (final UnsupportedEncodingException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
stringBuilder.append("&redirect=");
|
||||||
|
if (group.equals("0")) {
|
||||||
|
stringBuilder.append("admin.htm");
|
||||||
|
} else {
|
||||||
|
stringBuilder.append("explorer.htm");
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmcUrlPath + "ssosecurelogin.htm?" + stringBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.response;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.api.BaseResponse;
|
||||||
|
|
||||||
|
import com.cloud.serializer.Param;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class CloudianEnabledResponse extends BaseResponse {
|
||||||
|
@SerializedName(ApiConstants.ENABLED)
|
||||||
|
@Param(description = "the Cloudian connector enabled state")
|
||||||
|
private Boolean enabled;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.URL)
|
||||||
|
@Param(description = "the Cloudian Management Console base URL")
|
||||||
|
private String cmcUrl;
|
||||||
|
|
||||||
|
public void setEnabled(Boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCmcUrl(String cmcUrl) {
|
||||||
|
this.cmcUrl = cmcUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian.response;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.api.BaseResponse;
|
||||||
|
|
||||||
|
import com.cloud.serializer.Param;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class CloudianSsoLoginResponse extends BaseResponse {
|
||||||
|
@SerializedName(ApiConstants.URL)
|
||||||
|
@Param(description = "the sso redirect url")
|
||||||
|
private String ssoRedirectUrl;
|
||||||
|
|
||||||
|
public void setSsoRedirectUrl(final String ssoRedirectUrl) {
|
||||||
|
this.ssoRedirectUrl = ssoRedirectUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,416 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package org.apache.cloudstack.cloudian;
|
||||||
|
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.containing;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.post;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.put;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.cloudian.client.CloudianClient;
|
||||||
|
import org.apache.cloudstack.cloudian.client.CloudianGroup;
|
||||||
|
import org.apache.cloudstack.cloudian.client.CloudianUser;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.cloud.utils.exception.CloudRuntimeException;
|
||||||
|
import com.github.tomakehurst.wiremock.client.BasicCredentials;
|
||||||
|
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||||
|
|
||||||
|
public class CloudianClientTest {
|
||||||
|
private final int port = 14333;
|
||||||
|
private final int timeout = 2;
|
||||||
|
private final String adminUsername = "admin";
|
||||||
|
private final String adminPassword = "public";
|
||||||
|
private CloudianClient client;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public WireMockRule wireMockRule = new WireMockRule(port);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
client = new CloudianClient("localhost", port, "http", adminUsername, adminPassword, false, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CloudianUser getTestUser() {
|
||||||
|
final CloudianUser user = new CloudianUser();
|
||||||
|
user.setActive(true);
|
||||||
|
user.setUserId("someUserId");
|
||||||
|
user.setGroupId("someGroupId");
|
||||||
|
user.setUserType(CloudianUser.USER);
|
||||||
|
user.setFullName("John Doe");
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CloudianGroup getTestGroup() {
|
||||||
|
final CloudianGroup group = new CloudianGroup();
|
||||||
|
group.setActive(true);
|
||||||
|
group.setGroupId("someGroupId");
|
||||||
|
group.setGroupName("someGroupName");
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
//////////////// General API tests /////////////////////
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Test(expected = CloudRuntimeException.class)
|
||||||
|
public void testRequestTimeout() {
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/group/list"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withStatus(200)
|
||||||
|
.withFixedDelay(2 * timeout * 1000)
|
||||||
|
.withBody("")));
|
||||||
|
client.listGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicAuth() {
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/group/list"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withBody("[]")));
|
||||||
|
client.listGroups();
|
||||||
|
verify(getRequestedFor(urlEqualTo("/group/list"))
|
||||||
|
.withBasicAuth(new BasicCredentials(adminUsername, adminPassword)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ServerApiException.class)
|
||||||
|
public void testBasicAuthFailure() {
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/user"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(401)
|
||||||
|
.withBody("")));
|
||||||
|
client.listUser("someUserId", "somegGroupId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
//////////////// User API tests /////////////////////
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addUserAccount() {
|
||||||
|
wireMockRule.stubFor(put(urlEqualTo("/user"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianUser user = getTestUser();
|
||||||
|
boolean result = client.addUser(user);
|
||||||
|
Assert.assertTrue(result);
|
||||||
|
verify(putRequestedFor(urlEqualTo("/user"))
|
||||||
|
.withRequestBody(containing("userId\":\"" + user.getUserId()))
|
||||||
|
.withHeader("Content-Type", equalTo("application/json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addUserAccountFail() {
|
||||||
|
wireMockRule.stubFor(put(urlEqualTo("/user"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(400)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianUser user = getTestUser();
|
||||||
|
boolean result = client.addUser(user);
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listUserAccount() {
|
||||||
|
final String userId = "someUser";
|
||||||
|
final String groupId = "someGroup";
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/user?.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("{\"userId\":\"someUser\",\"userType\":\"User\",\"fullName\":\"John Doe (jdoe)\",\"emailAddr\":\"j@doe.com\",\"address1\":null,\"address2\":null,\"city\":null,\"state\":null,\"zip\":null,\"country\":null,\"phone\":null,\"groupId\":\"someGroup\",\"website\":null,\"active\":\"true\",\"canonicalUserId\":\"b3940886468689d375ebf8747b151c37\",\"ldapEnabled\":false}")));
|
||||||
|
|
||||||
|
final CloudianUser user = client.listUser(userId, groupId);
|
||||||
|
Assert.assertEquals(user.getActive(), true);
|
||||||
|
Assert.assertEquals(user.getUserId(), userId);
|
||||||
|
Assert.assertEquals(user.getGroupId(), groupId);
|
||||||
|
Assert.assertEquals(user.getUserType(), "User");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listUserAccountFail() {
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/user?.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianUser user = client.listUser("abc", "xyz");
|
||||||
|
Assert.assertNull(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listUserAccounts() {
|
||||||
|
final String groupId = "someGroup";
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/user/list?.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("[{\"userId\":\"someUser\",\"userType\":\"User\",\"fullName\":\"John Doe (jdoe)\",\"emailAddr\":\"j@doe.com\",\"address1\":null,\"address2\":null,\"city\":null,\"state\":null,\"zip\":null,\"country\":null,\"phone\":null,\"groupId\":\"someGroup\",\"website\":null,\"active\":\"true\",\"canonicalUserId\":\"b3940886468689d375ebf8747b151c37\",\"ldapEnabled\":false}]")));
|
||||||
|
|
||||||
|
final List<CloudianUser> users = client.listUsers(groupId);
|
||||||
|
Assert.assertEquals(users.size(), 1);
|
||||||
|
Assert.assertEquals(users.get(0).getActive(), true);
|
||||||
|
Assert.assertEquals(users.get(0).getGroupId(), groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyListUsersResponse() {
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/user/list"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withStatus(204)
|
||||||
|
.withBody("")));
|
||||||
|
Assert.assertTrue(client.listUsers("someGroup").size() == 0);
|
||||||
|
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/user"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withStatus(204)
|
||||||
|
.withBody("")));
|
||||||
|
Assert.assertNull(client.listUser("someUserId", "someGroupId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listUserAccountsFail() {
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/user/list?.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final List<CloudianUser> users = client.listUsers("xyz");
|
||||||
|
Assert.assertEquals(users.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateUserAccount() {
|
||||||
|
wireMockRule.stubFor(post(urlEqualTo("/user"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianUser user = getTestUser();
|
||||||
|
boolean result = client.updateUser(user);
|
||||||
|
Assert.assertTrue(result);
|
||||||
|
verify(postRequestedFor(urlEqualTo("/user"))
|
||||||
|
.withRequestBody(containing("userId\":\"" + user.getUserId()))
|
||||||
|
.withHeader("Content-Type", equalTo("application/json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateUserAccountFail() {
|
||||||
|
wireMockRule.stubFor(post(urlEqualTo("/user"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(400)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
boolean result = client.updateUser(getTestUser());
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeUserAccount() {
|
||||||
|
wireMockRule.stubFor(delete(urlPathMatching("/user.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withBody("")));
|
||||||
|
final CloudianUser user = getTestUser();
|
||||||
|
boolean result = client.removeUser(user.getUserId(), user.getGroupId());
|
||||||
|
Assert.assertTrue(result);
|
||||||
|
verify(deleteRequestedFor(urlPathMatching("/user.*"))
|
||||||
|
.withQueryParam("userId", equalTo(user.getUserId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeUserAccountFail() {
|
||||||
|
wireMockRule.stubFor(delete(urlPathMatching("/user.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(400)
|
||||||
|
.withBody("")));
|
||||||
|
final CloudianUser user = getTestUser();
|
||||||
|
boolean result = client.removeUser(user.getUserId(), user.getGroupId());
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
//////////////// Group API tests /////////////////////
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addGroup() {
|
||||||
|
wireMockRule.stubFor(put(urlEqualTo("/group"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianGroup group = getTestGroup();
|
||||||
|
boolean result = client.addGroup(group);
|
||||||
|
Assert.assertTrue(result);
|
||||||
|
verify(putRequestedFor(urlEqualTo("/group"))
|
||||||
|
.withRequestBody(containing("groupId\":\"someGroupId"))
|
||||||
|
.withHeader("Content-Type", equalTo("application/json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addGroupFail() throws Exception {
|
||||||
|
wireMockRule.stubFor(put(urlEqualTo("/group"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(400)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianGroup group = getTestGroup();
|
||||||
|
boolean result = client.addGroup(group);
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listGroup() {
|
||||||
|
final String groupId = "someGroup";
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/group.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("{\"groupId\":\"someGroup\",\"groupName\":\"/someDomain\",\"ldapGroup\":null,\"active\":\"true\",\"ldapEnabled\":false,\"ldapServerURL\":null,\"ldapUserDNTemplate\":null,\"ldapSearch\":null,\"ldapSearchUserBase\":null,\"ldapMatchAttribute\":null}")));
|
||||||
|
|
||||||
|
final CloudianGroup group = client.listGroup(groupId);
|
||||||
|
Assert.assertEquals(group.getActive(), true);
|
||||||
|
Assert.assertEquals(group.getGroupId(), groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listGroupFail() {
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/group.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianGroup group = client.listGroup("xyz");
|
||||||
|
Assert.assertNull(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listGroups() {
|
||||||
|
final String groupId = "someGroup";
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/group/list"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("[{\"groupId\":\"someGroup\",\"groupName\":\"/someDomain\",\"ldapGroup\":null,\"active\":\"true\",\"ldapEnabled\":false,\"ldapServerURL\":null,\"ldapUserDNTemplate\":null,\"ldapSearch\":null,\"ldapSearchUserBase\":null,\"ldapMatchAttribute\":null}]")));
|
||||||
|
|
||||||
|
final List<CloudianGroup> groups = client.listGroups();
|
||||||
|
Assert.assertEquals(groups.size(), 1);
|
||||||
|
Assert.assertEquals(groups.get(0).getActive(), true);
|
||||||
|
Assert.assertEquals(groups.get(0).getGroupId(), groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listGroupsFail() {
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/group/list"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final List<CloudianGroup> groups = client.listGroups();
|
||||||
|
Assert.assertEquals(groups.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyListGroupResponse() {
|
||||||
|
wireMockRule.stubFor(get(urlEqualTo("/group/list"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withStatus(204)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
Assert.assertTrue(client.listGroups().size() == 0);
|
||||||
|
|
||||||
|
|
||||||
|
wireMockRule.stubFor(get(urlPathMatching("/group"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withHeader("Content-Type", "application/json")
|
||||||
|
.withStatus(204)
|
||||||
|
.withBody("")));
|
||||||
|
Assert.assertNull(client.listGroup("someGroup"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateGroup() {
|
||||||
|
wireMockRule.stubFor(post(urlEqualTo("/group"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
final CloudianGroup group = getTestGroup();
|
||||||
|
boolean result = client.updateGroup(group);
|
||||||
|
Assert.assertTrue(result);
|
||||||
|
verify(postRequestedFor(urlEqualTo("/group"))
|
||||||
|
.withRequestBody(containing("groupId\":\"" + group.getGroupId()))
|
||||||
|
.withHeader("Content-Type", equalTo("application/json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateGroupFail() {
|
||||||
|
wireMockRule.stubFor(post(urlEqualTo("/group"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(400)
|
||||||
|
.withBody("")));
|
||||||
|
|
||||||
|
boolean result = client.updateGroup(getTestGroup());
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeGroup() {
|
||||||
|
wireMockRule.stubFor(delete(urlPathMatching("/group.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(200)
|
||||||
|
.withBody("")));
|
||||||
|
final CloudianGroup group = getTestGroup();
|
||||||
|
boolean result = client.removeGroup(group.getGroupId());
|
||||||
|
Assert.assertTrue(result);
|
||||||
|
verify(deleteRequestedFor(urlPathMatching("/group.*"))
|
||||||
|
.withQueryParam("groupId", equalTo(group.getGroupId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeGroupFail() {
|
||||||
|
wireMockRule.stubFor(delete(urlPathMatching("/group.*"))
|
||||||
|
.willReturn(aResponse()
|
||||||
|
.withStatus(400)
|
||||||
|
.withBody("")));
|
||||||
|
final CloudianGroup group = getTestGroup();
|
||||||
|
boolean result = client.removeGroup(group.getGroupId());
|
||||||
|
Assert.assertFalse(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -107,6 +107,7 @@
|
|||||||
<module>network-elements/vxlan</module>
|
<module>network-elements/vxlan</module>
|
||||||
<module>network-elements/globodns</module>
|
<module>network-elements/globodns</module>
|
||||||
<module>database/quota</module>
|
<module>database/quota</module>
|
||||||
|
<module>integrations/cloudian</module>
|
||||||
<module>integrations/prometheus</module>
|
<module>integrations/prometheus</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
|||||||
1
pom.xml
1
pom.xml
@ -124,6 +124,7 @@
|
|||||||
<cs.cxf.version>3.1.4</cs.cxf.version>
|
<cs.cxf.version>3.1.4</cs.cxf.version>
|
||||||
<cs.groovy.version>2.4.7</cs.groovy.version>
|
<cs.groovy.version>2.4.7</cs.groovy.version>
|
||||||
<cs.nitro.version>10.1</cs.nitro.version>
|
<cs.nitro.version>10.1</cs.nitro.version>
|
||||||
|
<cs.wiremock.version>2.8.0</cs.wiremock.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
|
|||||||
18
ui/plugins/cloudian/cloudian.css
Normal file
18
ui/plugins/cloudian/cloudian.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
66
ui/plugins/cloudian/cloudian.js
Normal file
66
ui/plugins/cloudian/cloudian.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
(function (cloudStack) {
|
||||||
|
cloudStack.plugins.cloudian = function(plugin) {
|
||||||
|
|
||||||
|
plugin.ui.addSection({
|
||||||
|
id: 'cloudian',
|
||||||
|
title: 'Cloudian Storage',
|
||||||
|
showOnNavigation: true,
|
||||||
|
preFilter: function(args) {
|
||||||
|
var pluginEnabled = false;
|
||||||
|
$.ajax({
|
||||||
|
url: createURL('cloudianIsEnabled'),
|
||||||
|
async: false,
|
||||||
|
success: function(json) {
|
||||||
|
var response = json.cloudianisenabledresponse.cloudianisenabled;
|
||||||
|
pluginEnabled = response.enabled;
|
||||||
|
if (pluginEnabled) {
|
||||||
|
var cloudianLogoutUrl = response.url + "logout.htm?";
|
||||||
|
onLogoutCallback = function() {
|
||||||
|
g_loginResponse = null;
|
||||||
|
var csUrl = window.location.href;
|
||||||
|
var redirect = "redirect=" + encodeURIComponent(csUrl);
|
||||||
|
window.location.replace(cloudianLogoutUrl + redirect);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return pluginEnabled;
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function() {
|
||||||
|
var description = 'Cloudian Management Console should open in another window.';
|
||||||
|
$.ajax({
|
||||||
|
url: createURL('cloudianSsoLogin'),
|
||||||
|
async: false,
|
||||||
|
success: function(json) {
|
||||||
|
var response = json.cloudianssologinresponse.cloudianssologin;
|
||||||
|
var cmcWindow = window.open(response.url, "CMCWindow");
|
||||||
|
cmcWindow.focus();
|
||||||
|
},
|
||||||
|
error: function(data) {
|
||||||
|
description = 'Single-Sign-On failed for Cloudian Management Console. Please ask your administrator to fix integration issues.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return $('<div style="margin: 20px;">').html(description);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}(cloudStack));
|
||||||
25
ui/plugins/cloudian/config.js
Normal file
25
ui/plugins/cloudian/config.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
// or more contributor license agreements. See the NOTICE file
|
||||||
|
// distributed with this work for additional information
|
||||||
|
// regarding copyright ownership. The ASF licenses this file
|
||||||
|
// to you under the Apache License, Version 2.0 (the
|
||||||
|
// "License"); you may not use this file except in compliance
|
||||||
|
// with the License. You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing,
|
||||||
|
// software distributed under the License is distributed on an
|
||||||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
// KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
(function (cloudStack) {
|
||||||
|
cloudStack.plugins.cloudian.config = {
|
||||||
|
title: 'Cloudian Storage',
|
||||||
|
desc: 'Cloudian Storage',
|
||||||
|
externalLink: 'https://cloudian.com/',
|
||||||
|
authorName: 'Cloudian Inc.',
|
||||||
|
authorEmail: 'info@cloudian.com '
|
||||||
|
};
|
||||||
|
}(cloudStack));
|
||||||
BIN
ui/plugins/cloudian/icon.png
Normal file
BIN
ui/plugins/cloudian/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@ -17,6 +17,7 @@
|
|||||||
(function($, cloudStack) {
|
(function($, cloudStack) {
|
||||||
cloudStack.plugins = [
|
cloudStack.plugins = [
|
||||||
//'testPlugin',
|
//'testPlugin',
|
||||||
|
'cloudian',
|
||||||
'quota'
|
'quota'
|
||||||
];
|
];
|
||||||
}(jQuery, cloudStack));
|
}(jQuery, cloudStack));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user