mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-02 20:02:29 +01:00
Merge branch 'main' of https://github.com/apache/cloudstack into nsx-integration
This commit is contained in:
commit
20cb9f56f3
@ -70,6 +70,8 @@ public interface AccountService {
|
||||
|
||||
UserAccount getActiveUserAccount(String username, Long domainId);
|
||||
|
||||
List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId);
|
||||
|
||||
UserAccount updateUser(UpdateUserCmd updateUserCmd);
|
||||
|
||||
Account getActiveAccountById(long accountId);
|
||||
|
||||
@ -24,7 +24,7 @@ public interface User extends OwnedBy, InternalIdentity {
|
||||
|
||||
// UNKNOWN and NATIVE can be used interchangeably
|
||||
public enum Source {
|
||||
LDAP, SAML2, SAML2DISABLED, UNKNOWN, NATIVE
|
||||
OAUTH2, LDAP, SAML2, SAML2DISABLED, UNKNOWN, NATIVE
|
||||
}
|
||||
|
||||
public static final long UID_SYSTEM = 1;
|
||||
|
||||
@ -595,6 +595,8 @@ public class ApiConstants {
|
||||
public static final String SERVICE_CAPABILITY_LIST = "servicecapabilitylist";
|
||||
public static final String CAN_CHOOSE_SERVICE_CAPABILITY = "canchooseservicecapability";
|
||||
public static final String PROVIDER = "provider";
|
||||
public static final String OAUTH_PROVIDER = "oauthprovider";
|
||||
public static final String OAUTH_SECRET_KEY = "secretkey";
|
||||
public static final String MANAGED = "managed";
|
||||
public static final String CAPACITY_BYTES = "capacitybytes";
|
||||
public static final String CAPACITY_IOPS = "capacityiops";
|
||||
@ -1069,6 +1071,9 @@ public class ApiConstants {
|
||||
public static final String VNF_CONFIGURE_MANAGEMENT = "vnfconfiguremanagement";
|
||||
public static final String VNF_CIDR_LIST = "vnfcidrlist";
|
||||
|
||||
public static final String CLIENT_ID = "clientid";
|
||||
public static final String REDIRECT_URI = "redirecturi";
|
||||
|
||||
/**
|
||||
* This enum specifies IO Drivers, each option controls specific policies on I/O.
|
||||
* Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0).
|
||||
|
||||
@ -95,5 +95,4 @@ public class CreateSSHKeyPairCmd extends BaseCmd {
|
||||
response.setObjectName("keypair");
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,5 +76,4 @@ public class ListUserDataCmd extends BaseListProjectAndAccountResourcesCmd {
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,5 +142,4 @@ public class RegisterUserDataCmd extends BaseCmd {
|
||||
response.setObjectName(ApiConstants.USER_DATA);
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
// 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.auth;
|
||||
|
||||
import com.cloud.utils.component.Adapter;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public interface UserOAuth2Authenticator extends Adapter {
|
||||
/**
|
||||
* Returns the unique name of the provider
|
||||
* @return returns provider name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns description about the OAuth2 provider plugin
|
||||
* @return returns description
|
||||
*/
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Verifies if the logged in user is
|
||||
* @return returns true if its valid user
|
||||
*/
|
||||
boolean verifyUser(String email, String secretCode);
|
||||
|
||||
/**
|
||||
* Verifies the code provided by provider and fetches email
|
||||
* @return returns email
|
||||
*/
|
||||
String verifyCodeAndFetchEmail(String secretCode);
|
||||
|
||||
|
||||
/**
|
||||
* Fetches email using the accessToken
|
||||
* @return returns email
|
||||
*/
|
||||
String getUserEmailAddress() throws CloudRuntimeException;
|
||||
}
|
||||
@ -161,6 +161,11 @@
|
||||
<artifactId>cloud-plugin-user-authenticator-md5</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-user-authenticator-oauth2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-user-authenticator-pbkdf2</artifactId>
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
<property name="orderConfigKey" value="user.authenticators.order" />
|
||||
<property name="excludeKey" value="user.authenticators.exclude" />
|
||||
<property name="orderConfigDefault" value="PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT" />
|
||||
<property name="orderConfigDefault" value="PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT,OAUTH2" />
|
||||
</bean>
|
||||
|
||||
<bean id="userTwoFactorAuthenticatorsRegistry"
|
||||
@ -47,7 +47,7 @@
|
||||
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
<property name="orderConfigKey" value="pluggableApi.authenticators.order" />
|
||||
<property name="excludeKey" value="pluggableApi.authenticators.exclude" />
|
||||
<property name="orderConfigDefault" value="SAML2Auth" />
|
||||
<property name="orderConfigDefault" value="SAML2Auth,OAUTH2Auth" />
|
||||
</bean>
|
||||
|
||||
<bean id="userPasswordEncodersRegistry"
|
||||
|
||||
@ -27,6 +27,8 @@ public interface UserAccountDao extends GenericDao<UserAccountVO, Long> {
|
||||
|
||||
UserAccount getUserAccount(String username, Long domainId);
|
||||
|
||||
List<UserAccountVO> getUserAccountByEmail(String email, Long domainId);
|
||||
|
||||
boolean validateUsernameInDomain(String username, Long domainId);
|
||||
|
||||
UserAccount getUserByApiKey(String apiKey);
|
||||
|
||||
@ -59,6 +59,18 @@ public class UserAccountDaoImpl extends GenericDaoBase<UserAccountVO, Long> impl
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserAccountVO> getUserAccountByEmail(String email, Long domainId) {
|
||||
if (email == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SearchCriteria<UserAccountVO> sc = createSearchCriteria();
|
||||
sc.addAnd("email", SearchCriteria.Op.EQ, email);
|
||||
sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateUsernameInDomain(String username, Long domainId) {
|
||||
UserAccount userAcct = getUserAccount(username, domainId);
|
||||
|
||||
@ -706,3 +706,31 @@ FROM
|
||||
`cloud`.`vpc_offering_details` AS `offering_details` ON `offering_details`.`offering_id` = `vpc_offerings`.`id` AND `offering_details`.`name`='internetprotocol'
|
||||
GROUP BY
|
||||
`vpc_offerings`.`id`;
|
||||
|
||||
UPDATE `cloud`.`configuration` SET
|
||||
`options` = concat(`options`, ',OAUTH2'),
|
||||
`default_value` = concat(`default_value`, ',OAUTH2'),
|
||||
`value` = concat(`value`, ',OAUTH2')
|
||||
WHERE `name` = 'user.authenticators.order' ;
|
||||
|
||||
UPDATE `cloud`.`configuration` SET
|
||||
`options` = concat(`options`, ',OAUTH2Auth'),
|
||||
`default_value` = concat(`default_value`, ',OAUTH2Auth'),
|
||||
`value` = concat(`value`, ',OAUTH2Auth')
|
||||
where `name` = 'pluggableApi.authenticators.order' ;
|
||||
|
||||
-- Create table for OAuth provider details
|
||||
DROP TABLE IF EXISTS `cloud`.`oauth_provider`;
|
||||
CREATE TABLE `cloud`.`oauth_provider` (
|
||||
`id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
|
||||
`uuid` varchar(40) NOT NULL COMMENT 'unique identifier',
|
||||
`description` varchar(1024) COMMENT 'description of the provider',
|
||||
`provider` varchar(40) NOT NULL COMMENT 'name of the provider',
|
||||
`client_id` varchar(255) NOT NULL COMMENT 'client id which is configured in the provider',
|
||||
`secret_key` varchar(255) NOT NULL COMMENT 'secret key which is configured in the provider',
|
||||
`redirect_uri` varchar(255) NOT NULL COMMENT 'redirect uri which is configured in the provider',
|
||||
`enabled` int(1) NOT NULL DEFAULT 1 COMMENT 'Enabled or disabled',
|
||||
`created` datetime NOT NULL COMMENT 'date created',
|
||||
`removed` datetime COMMENT 'date removed if not null',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
@ -176,6 +176,11 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getActiveUser(long arg0) {
|
||||
return _systemUser;
|
||||
|
||||
@ -140,6 +140,7 @@
|
||||
|
||||
<module>user-authenticators/ldap</module>
|
||||
<module>user-authenticators/md5</module>
|
||||
<module>user-authenticators/oauth2</module>
|
||||
<module>user-authenticators/pbkdf2</module>
|
||||
<module>user-authenticators/plain-text</module>
|
||||
<module>user-authenticators/saml2</module>
|
||||
|
||||
63
plugins/user-authenticators/oauth2/pom.xml
Normal file
63
plugins/user-authenticators/oauth2/pom.xml
Normal file
@ -0,0 +1,63 @@
|
||||
<!--
|
||||
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-user-authenticator-oauth2</artifactId>
|
||||
<name>Apache CloudStack Plugin - User Authenticator OAuth2</name>
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-plugins</artifactId>
|
||||
<version>4.19.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.apis</groupId>
|
||||
<artifactId>google-api-services-docs</artifactId>
|
||||
<version>v1-rev20220609-1.32.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.apis</groupId>
|
||||
<artifactId>google-api-services-oauth2</artifactId>
|
||||
<version>v2-rev20200213-1.32.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.oauth-client</groupId>
|
||||
<artifactId>google-oauth-client-servlet</artifactId>
|
||||
<version>1.34.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.http-client</groupId>
|
||||
<artifactId>google-http-client-jackson2</artifactId>
|
||||
<version>1.20.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,61 @@
|
||||
//
|
||||
// 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.oauth2;
|
||||
|
||||
import com.cloud.utils.component.PluggableService;
|
||||
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.oauth2.api.command.RegisterOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OAuth2AuthManager extends PluggableAPIAuthenticator, PluggableService {
|
||||
public static ConfigKey<Boolean> OAuth2IsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "oauth2.enabled", "false",
|
||||
"Indicates whether OAuth plugin is enabled or not", false);
|
||||
public static final ConfigKey<String> OAuth2Plugins = new ConfigKey<String>("Advanced", String.class, "oauth2.plugins", "google,github",
|
||||
"List of OAuth plugins", true);
|
||||
public static final ConfigKey<String> OAuth2PluginsExclude = new ConfigKey<String>("Advanced", String.class, "oauth2.plugins.exclude", "",
|
||||
"List of OAuth plugins which are excluded", true);
|
||||
|
||||
/**
|
||||
* Lists user OAuth2 provider plugins
|
||||
* @return list of providers
|
||||
*/
|
||||
List<UserOAuth2Authenticator> listUserOAuth2AuthenticationProviders();
|
||||
|
||||
/**
|
||||
* Finds user OAuth2 provider by name
|
||||
* @param providerName name of the provider
|
||||
* @return OAuth2 provider
|
||||
*/
|
||||
UserOAuth2Authenticator getUserOAuth2AuthenticationProvider(final String providerName);
|
||||
|
||||
String verifyCodeAndFetchEmail(String code, String provider);
|
||||
|
||||
OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd);
|
||||
|
||||
List<OauthProviderVO> listOauthProviders(String provider, String uuid);
|
||||
|
||||
boolean deleteOauthProvider(Long id);
|
||||
|
||||
OauthProviderVO updateOauthProvider(UpdateOAuthProviderCmd cmd);
|
||||
}
|
||||
@ -0,0 +1,233 @@
|
||||
//
|
||||
// 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.oauth2;
|
||||
|
||||
import com.cloud.user.dao.UserDao;
|
||||
import com.cloud.utils.component.Manager;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.oauth2.api.command.DeleteOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.ListOAuthProvidersCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.OauthLoginAPIAuthenticatorCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.RegisterOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.VerifyOAuthCodeAndGetUserCmd;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthManager, Manager, Configurable {
|
||||
private static final Logger s_logger = Logger.getLogger(OAuth2AuthManagerImpl.class);
|
||||
@Inject
|
||||
private UserDao _userDao;
|
||||
|
||||
@Inject
|
||||
protected OauthProviderDao _oauthProviderDao;
|
||||
|
||||
protected static Map<String, UserOAuth2Authenticator> userOAuth2AuthenticationProvidersMap = new HashMap<>();
|
||||
|
||||
private List<UserOAuth2Authenticator> userOAuth2AuthenticationProviders;
|
||||
|
||||
@Override
|
||||
public List<Class<?>> getAuthCommands() {
|
||||
List<Class<?>> cmdList = new ArrayList<Class<?>>();
|
||||
cmdList.add(OauthLoginAPIAuthenticatorCmd.class);
|
||||
cmdList.add(ListOAuthProvidersCmd.class);
|
||||
cmdList.add(VerifyOAuthCodeAndGetUserCmd.class);
|
||||
return cmdList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
if (isOAuthPluginEnabled()) {
|
||||
s_logger.info("OAUTH plugin loaded");
|
||||
initializeUserOAuth2AuthenticationProvidersMap();
|
||||
} else {
|
||||
s_logger.info("OAUTH plugin not enabled so not loading");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isOAuthPluginEnabled() {
|
||||
return OAuth2IsPluginEnabled.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<?>> getCommands() {
|
||||
List<Class<?>> cmdList = new ArrayList<Class<?>>();
|
||||
cmdList.add(RegisterOAuthProviderCmd.class);
|
||||
cmdList.add(DeleteOAuthProviderCmd.class);
|
||||
cmdList.add(UpdateOAuthProviderCmd.class);
|
||||
|
||||
return cmdList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserOAuth2Authenticator> listUserOAuth2AuthenticationProviders() {
|
||||
return userOAuth2AuthenticationProviders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserOAuth2Authenticator getUserOAuth2AuthenticationProvider(String providerName) {
|
||||
if (StringUtils.isEmpty(providerName)) {
|
||||
throw new CloudRuntimeException("OAuth2 authentication provider name is empty");
|
||||
}
|
||||
if (!userOAuth2AuthenticationProvidersMap.containsKey(providerName.toLowerCase())) {
|
||||
throw new CloudRuntimeException(String.format("Failed to find OAuth2 authentication provider by the name: %s.", providerName));
|
||||
}
|
||||
return userOAuth2AuthenticationProvidersMap.get(providerName.toLowerCase());
|
||||
}
|
||||
|
||||
public List<UserOAuth2Authenticator> getUserOAuth2AuthenticationProviders() {
|
||||
return userOAuth2AuthenticationProviders;
|
||||
}
|
||||
|
||||
public void setUserOAuth2AuthenticationProviders(final List<UserOAuth2Authenticator> userOAuth2AuthenticationProviders) {
|
||||
this.userOAuth2AuthenticationProviders = userOAuth2AuthenticationProviders;
|
||||
}
|
||||
|
||||
protected void initializeUserOAuth2AuthenticationProvidersMap() {
|
||||
if (userOAuth2AuthenticationProviders != null) {
|
||||
for (final UserOAuth2Authenticator userOAuth2Authenticator : userOAuth2AuthenticationProviders) {
|
||||
userOAuth2AuthenticationProvidersMap.put(userOAuth2Authenticator.getName().toLowerCase(), userOAuth2Authenticator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String code, String provider) {
|
||||
UserOAuth2Authenticator authenticator = getUserOAuth2AuthenticationProvider(provider);
|
||||
String email = authenticator.verifyCodeAndFetchEmail(code);
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd) {
|
||||
String description = cmd.getDescription();
|
||||
String provider = cmd.getProvider();
|
||||
String clientId = cmd.getClientId();
|
||||
String redirectUri = cmd.getRedirectUri();
|
||||
String secretKey = cmd.getSecretKey();
|
||||
|
||||
if (!isOAuthPluginEnabled()) {
|
||||
throw new CloudRuntimeException("OAuth is not enabled, please enable to register");
|
||||
}
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProvider(provider);
|
||||
if (providerVO != null) {
|
||||
throw new CloudRuntimeException(String.format("Provider with the name %s is already registered", provider));
|
||||
}
|
||||
|
||||
return saveOauthProvider(provider, description, clientId, secretKey, redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OauthProviderVO> listOauthProviders(String provider, String uuid) {
|
||||
List<OauthProviderVO> providers;
|
||||
if (uuid != null) {
|
||||
providers = Collections.singletonList(_oauthProviderDao.findByUuid(uuid));
|
||||
} else if (StringUtils.isNotBlank(provider)) {
|
||||
providers = Collections.singletonList(_oauthProviderDao.findByProvider(provider));
|
||||
} else {
|
||||
providers = _oauthProviderDao.listAll();
|
||||
}
|
||||
return providers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OauthProviderVO updateOauthProvider(UpdateOAuthProviderCmd cmd) {
|
||||
Long id = cmd.getId();
|
||||
String description = cmd.getDescription();
|
||||
String clientId = cmd.getClientId();
|
||||
String redirectUri = cmd.getRedirectUri();
|
||||
String secretKey = cmd.getSecretKey();
|
||||
Boolean enabled = cmd.getEnabled();
|
||||
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findById(id);
|
||||
if (providerVO == null) {
|
||||
throw new CloudRuntimeException("Provider with the given id is not there");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(description)) {
|
||||
providerVO.setDescription(description);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(clientId)) {
|
||||
providerVO.setClientId(clientId);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(redirectUri)) {
|
||||
providerVO.setRedirectUri(redirectUri);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(secretKey)) {
|
||||
providerVO.setSecretKey(secretKey);
|
||||
}
|
||||
if (enabled != null) {
|
||||
providerVO.setEnabled(enabled);
|
||||
}
|
||||
|
||||
_oauthProviderDao.update(id, providerVO);
|
||||
|
||||
return _oauthProviderDao.findById(id);
|
||||
}
|
||||
|
||||
private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri) {
|
||||
final OauthProviderVO oauthProviderVO = new OauthProviderVO();
|
||||
|
||||
oauthProviderVO.setProvider(provider);
|
||||
oauthProviderVO.setDescription(description);
|
||||
oauthProviderVO.setClientId(clientId);
|
||||
oauthProviderVO.setSecretKey(secretKey);
|
||||
oauthProviderVO.setRedirectUri(redirectUri);
|
||||
oauthProviderVO.setEnabled(true);
|
||||
|
||||
_oauthProviderDao.persist(oauthProviderVO);
|
||||
|
||||
return oauthProviderVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteOauthProvider(Long id) {
|
||||
return _oauthProviderDao.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return "OAUTH2-PLUGIN";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {OAuth2IsPluginEnabled, OAuth2Plugins, OAuth2PluginsExclude};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
//
|
||||
// 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.oauth2;
|
||||
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.user.dao.UserDao;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.auth.UserAuthenticator;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
public class OAuth2UserAuthenticator extends AdapterBase implements UserAuthenticator {
|
||||
public static final Logger s_logger = Logger.getLogger(OAuth2UserAuthenticator.class);
|
||||
|
||||
@Inject
|
||||
private UserAccountDao _userAccountDao;
|
||||
@Inject
|
||||
private UserDao _userDao;
|
||||
|
||||
@Inject
|
||||
private OAuth2AuthManager _userOAuth2mgr;
|
||||
|
||||
@Override
|
||||
public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Trying OAuth2 auth for user: " + username);
|
||||
}
|
||||
|
||||
final UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
|
||||
if (userAccount == null) {
|
||||
s_logger.debug("Unable to find user with " + username + " in domain " + domainId + ", or user source is not OAUTH2");
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
|
||||
} else {
|
||||
User user = _userDao.getUser(userAccount.getId());
|
||||
final String[] provider = (String[])requestParameters.get(ApiConstants.PROVIDER);
|
||||
final String[] emailArray = (String[])requestParameters.get(ApiConstants.EMAIL);
|
||||
final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE);
|
||||
String oauthProvider = ((provider == null) ? null : provider[0]);
|
||||
String email = ((emailArray == null) ? null : emailArray[0]);
|
||||
String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
|
||||
|
||||
UserOAuth2Authenticator authenticator = _userOAuth2mgr.getUserOAuth2AuthenticationProvider(oauthProvider);
|
||||
if (user != null && authenticator.verifyUser(email, secretCode)) {
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
|
||||
}
|
||||
}
|
||||
// Deny all by default
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String password) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
// 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.oauth2.api.command;
|
||||
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@APICommand(name = "deleteOauthProvider", description = "Deletes the registered OAuth provider", responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0")
|
||||
public class DeleteOAuthProviderCmd extends BaseCmd {
|
||||
public static final Logger s_logger = Logger.getLogger(DeleteOAuthProviderCmd.class.getName());
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = OauthProviderResponse.class, required = true, description = "id of the OAuth provider to be deleted")
|
||||
private Long id;
|
||||
|
||||
@Inject
|
||||
OAuth2AuthManager _oauthMgr;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.User;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
boolean result = _oauthMgr.deleteOauthProvider(getId());
|
||||
if (result) {
|
||||
SuccessResponse response = new SuccessResponse(getCommandName());
|
||||
this.setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete the OAuth provider");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
// 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.oauth2.api.command;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.user.Account;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseListCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticationType;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticator;
|
||||
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
@APICommand(name = "listOauthProvider", description = "List OAuth providers registered", responseObject = OauthProviderResponse.class, entityType = {},
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0")
|
||||
public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticator {
|
||||
public static final Logger s_logger = Logger.getLogger(ListOAuthProvidersCmd.class.getName());
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = OauthProviderResponse.class, description = "the ID of the OAuth provider")
|
||||
private String id;
|
||||
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider")
|
||||
private String provider;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
OAuth2AuthManager _oauth2mgr;
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return Account.Type.NORMAL.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ServerApiException {
|
||||
// We should never reach here
|
||||
throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, HttpServletRequest req, HttpServletResponse resp) throws ServerApiException {
|
||||
final String[] idArray = (String[])params.get(ApiConstants.ID);
|
||||
final String[] providerArray = (String[])params.get(ApiConstants.PROVIDER);
|
||||
if (ArrayUtils.isNotEmpty(idArray)) {
|
||||
id = idArray[0];
|
||||
}
|
||||
if (ArrayUtils.isNotEmpty(providerArray)) {
|
||||
provider = providerArray[0];
|
||||
}
|
||||
|
||||
List<OauthProviderVO> resultList = _oauth2mgr.listOauthProviders(provider, id);
|
||||
List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauth2mgr.listUserOAuth2AuthenticationProviders();
|
||||
List<String> authenticatorPluginNames = new ArrayList<>();
|
||||
for (UserOAuth2Authenticator authenticator : userOAuth2AuthenticatorPlugins) {
|
||||
String name = authenticator.getName();
|
||||
authenticatorPluginNames.add(name);
|
||||
}
|
||||
List<OauthProviderResponse> responses = new ArrayList<>();
|
||||
for (OauthProviderVO result : resultList) {
|
||||
OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri());
|
||||
if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
|
||||
r.setEnabled(true);
|
||||
} else {
|
||||
r.setEnabled(false);
|
||||
}
|
||||
r.setObjectName(ApiConstants.OAUTH_PROVIDER);
|
||||
responses.add(r);
|
||||
}
|
||||
|
||||
ListResponse<OauthProviderResponse> response = new ListResponse<>();
|
||||
response.setResponses(responses, resultList.size());
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
|
||||
return ApiResponseSerializer.toSerializedString(response, responseType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIAuthenticationType getAPIType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
|
||||
for (PluggableAPIAuthenticator authManager: authenticators) {
|
||||
if (authManager != null && authManager instanceof OAuth2AuthManager) {
|
||||
_oauth2mgr = (OAuth2AuthManager) authManager;
|
||||
}
|
||||
}
|
||||
if (_oauth2mgr == null) {
|
||||
s_logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
// 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.oauth2.api.command;
|
||||
|
||||
import com.cloud.api.ApiServlet;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.user.UserAccount;
|
||||
import org.apache.cloudstack.api.ApiServerService;
|
||||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.exception.CloudAuthenticationException;
|
||||
import com.cloud.user.Account;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticationType;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticator;
|
||||
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
|
||||
import org.apache.cloudstack.api.response.LoginCmdResponse;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import static org.apache.cloudstack.oauth2.OAuth2AuthManager.OAuth2IsPluginEnabled;
|
||||
|
||||
@APICommand(name = "oauthlogin", description = "Logs a user into the CloudStack after successful verification of OAuth secret code from the particular provider." +
|
||||
"A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired.",
|
||||
requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {}, since = "4.19.0")
|
||||
public class OauthLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
|
||||
|
||||
public static final Logger s_logger = Logger.getLogger(OauthLoginAPIAuthenticatorCmd.class.getName());
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider", required = true)
|
||||
private String provider;
|
||||
|
||||
@Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "Email id with which user tried to login using OAuth provider", required = true)
|
||||
private String email;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING, description = "Path of the domain that the user belongs to. Example: domain=/com/cloud/internal. If no domain is passed in, the ROOT (/) domain is assumed.")
|
||||
private String domain;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN__ID, type = CommandType.LONG, description = "The id of the domain that the user belongs to. If both domain and domainId are passed in, \"domainId\" parameter takes precedence.")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.SECRET_CODE, type = CommandType.STRING, description = "Code that is provided by OAuth provider (Eg. google, github) after successful login")
|
||||
private String secretCode;
|
||||
|
||||
@Inject
|
||||
ApiServerService _apiServer;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getDomainName() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public String getSecretCode() {
|
||||
return secretCode;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return Account.Type.NORMAL.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ServerApiException {
|
||||
// We should never reach here
|
||||
throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
|
||||
if (!OAuth2IsPluginEnabled.value()) {
|
||||
throw new CloudAuthenticationException("OAuth is not enabled in CloudStack, users cannot login using OAuth");
|
||||
}
|
||||
final String[] provider = (String[])params.get(ApiConstants.PROVIDER);
|
||||
final String[] emailArray = (String[])params.get(ApiConstants.EMAIL);
|
||||
final String[] secretCodeArray = (String[])params.get(ApiConstants.SECRET_CODE);
|
||||
|
||||
String oauthProvider = ((provider == null) ? null : provider[0]);
|
||||
String email = ((emailArray == null) ? null : emailArray[0]);
|
||||
String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
|
||||
if (StringUtils.isAnyEmpty(oauthProvider, email, secretCode)) {
|
||||
throw new CloudAuthenticationException("OAuth provider, email, secretCode any of these cannot be null");
|
||||
}
|
||||
|
||||
Long domainId = getDomainIdFromParams(params, auditTrailSb, responseType);
|
||||
final String[] domainName = (String[])params.get(ApiConstants.DOMAIN);
|
||||
String domain = getDomainName(auditTrailSb, domainName);
|
||||
|
||||
return doOauthAuthentication(session, domainId, domain, email, params, remoteAddress, responseType, auditTrailSb);
|
||||
}
|
||||
|
||||
private String doOauthAuthentication(HttpSession session, Long domainId, String domain, String email, Map<String, Object[]> params, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb) {
|
||||
String serializedResponse = null;
|
||||
|
||||
try {
|
||||
final Domain userDomain = _domainService.findDomainByIdOrPath(domainId, domain);
|
||||
if (userDomain != null) {
|
||||
domainId = userDomain.getId();
|
||||
} else {
|
||||
throw new CloudAuthenticationException("Unable to find the domain from the path " + domain);
|
||||
}
|
||||
final List<UserAccount> userAccounts = _accountService.getActiveUserAccountByEmail(email, domainId);
|
||||
if (CollectionUtils.isEmpty(userAccounts)) {
|
||||
throw new CloudAuthenticationException("User not found in CloudStack to login. If user belongs to any domain, please provide it.");
|
||||
}
|
||||
if (userAccounts.size() > 1) {
|
||||
throw new CloudAuthenticationException("Multiple Users found in CloudStack. If user belongs to any specific domain, please provide it.");
|
||||
}
|
||||
UserAccount userAccount = userAccounts.get(0);
|
||||
if (userAccount != null && User.Source.SAML2 == userAccount.getSource()) {
|
||||
throw new CloudAuthenticationException("User is not allowed CloudStack login");
|
||||
}
|
||||
return ApiResponseSerializer.toSerializedString(_apiServer.loginUser(session, userAccount.getUsername(), null, domainId, domain, remoteAddress, params),
|
||||
responseType);
|
||||
} catch (final CloudAuthenticationException ex) {
|
||||
ApiServlet.invalidateHttpSession(session, "fall through to API key,");
|
||||
String msg = String.format("%s", ex.getMessage() != null ?
|
||||
ex.getMessage() :
|
||||
"failed to authenticate user, check if username/password are correct");
|
||||
auditTrailSb.append(" " + ApiErrorCode.ACCOUNT_ERROR + " " + msg);
|
||||
serializedResponse = _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), msg, params, responseType);
|
||||
if (s_logger.isTraceEnabled()) {
|
||||
s_logger.trace(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// We should not reach here and if we do we throw an exception
|
||||
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, serializedResponse);
|
||||
}
|
||||
|
||||
protected Long getDomainIdFromParams(Map<String, Object[]> params, StringBuilder auditTrailSb, String responseType) {
|
||||
String[] domainIdArr = (String[])params.get(ApiConstants.DOMAIN_ID);
|
||||
|
||||
if (domainIdArr == null) {
|
||||
domainIdArr = (String[])params.get(ApiConstants.DOMAIN__ID);
|
||||
}
|
||||
Long domainId = null;
|
||||
if ((domainIdArr != null) && (domainIdArr.length > 0)) {
|
||||
try {
|
||||
//check if UUID is passed in for domain
|
||||
domainId = _apiServer.fetchDomainId(domainIdArr[0]);
|
||||
if (domainId == null) {
|
||||
domainId = Long.parseLong(domainIdArr[0]);
|
||||
}
|
||||
auditTrailSb.append(" domainid=" + domainId);// building the params for POST call
|
||||
} catch (final NumberFormatException e) {
|
||||
s_logger.warn("Invalid domain id entered by user");
|
||||
auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "Invalid domain id entered, please enter a valid one");
|
||||
throw new ServerApiException(ApiErrorCode.UNAUTHORIZED,
|
||||
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid domain id entered, please enter a valid one", params,
|
||||
responseType));
|
||||
}
|
||||
}
|
||||
return domainId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected String getDomainName(StringBuilder auditTrailSb, String[] domainName) {
|
||||
String domain = null;
|
||||
if (domainName != null) {
|
||||
domain = domainName[0];
|
||||
auditTrailSb.append(" domain=" + domain);
|
||||
if (domain != null) {
|
||||
// ensure domain starts with '/' and ends with '/'
|
||||
if (!domain.endsWith("/")) {
|
||||
domain += '/';
|
||||
}
|
||||
if (!domain.startsWith("/")) {
|
||||
domain = "/" + domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIAuthenticationType getAPIType() {
|
||||
return APIAuthenticationType.LOGIN_API;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
//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.oauth2.api.command;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.EntityExistsException;
|
||||
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "registerOauthProvider", responseObject = SuccessResponse.class, description = "Register the OAuth2 provider in CloudStack", since = "4.19.0")
|
||||
public class RegisterOAuthProviderCmd extends BaseCmd {
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = true, description = "Description of the OAuth Provider")
|
||||
private String description;
|
||||
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider from the list of OAuth providers supported in CloudStack", required = true)
|
||||
private String provider;
|
||||
|
||||
@Parameter(name = ApiConstants.CLIENT_ID, type = CommandType.STRING, description = "Client ID pre-registered in the specific OAuth provider", required = true)
|
||||
private String clientId;
|
||||
|
||||
@Parameter(name = ApiConstants.OAUTH_SECRET_KEY, type = CommandType.STRING, description = "Secret Key pre-registered in the specific OAuth provider", required = true)
|
||||
private String secretKey;
|
||||
|
||||
@Parameter(name = ApiConstants.REDIRECT_URI, type = CommandType.STRING, description = "Redirect URI pre-registered in the specific OAuth provider", required = true)
|
||||
private String redirectUri;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP,
|
||||
description = "Any OAuth provider details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].clientsecret=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")
|
||||
protected Map details;
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public Map getDetails() {
|
||||
if (MapUtils.isEmpty(details)) {
|
||||
return null;
|
||||
}
|
||||
Collection paramsCollection = this.details.values();
|
||||
return (Map) (paramsCollection.toArray())[0];
|
||||
}
|
||||
|
||||
@Inject
|
||||
OAuth2AuthManager _oauth2mgr;
|
||||
|
||||
@Override
|
||||
public void execute() throws ServerApiException, ConcurrentOperationException, EntityExistsException {
|
||||
OauthProviderVO provider = _oauth2mgr.registerOauthProvider(this);
|
||||
|
||||
OauthProviderResponse response = new OauthProviderResponse(provider.getUuid(), provider.getProvider(),
|
||||
provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri());
|
||||
response.setResponseName(getCommandName());
|
||||
response.setObjectName(ApiConstants.OAUTH_PROVIDER);
|
||||
setResponseObject(response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
// 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.oauth2.api.command;
|
||||
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@APICommand(name = "updateOauthProvider", description = "Updates the registered OAuth provider details", responseObject = OauthProviderResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0")
|
||||
public final class UpdateOAuthProviderCmd extends BaseCmd {
|
||||
public static final Logger s_logger = Logger.getLogger(UpdateOAuthProviderCmd.class.getName());
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = OauthProviderResponse.class, required = true, description = "id of the OAuth provider to be updated")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "Description of the OAuth Provider")
|
||||
private String description;
|
||||
|
||||
@Parameter(name = ApiConstants.CLIENT_ID, type = CommandType.STRING, description = "Client ID pre-registered in the specific OAuth provider")
|
||||
private String clientId;
|
||||
|
||||
@Parameter(name = ApiConstants.OAUTH_SECRET_KEY, type = CommandType.STRING, description = "Secret Key pre-registered in the specific OAuth provider")
|
||||
private String secretKey;
|
||||
|
||||
@Parameter(name = ApiConstants.REDIRECT_URI, type = CommandType.STRING, description = "Redirect URI pre-registered in the specific OAuth provider")
|
||||
private String redirectUri;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "OAuth provider will be enabled or disabled based on this value")
|
||||
private Boolean enabled;
|
||||
|
||||
@Inject
|
||||
OAuth2AuthManager _oauthMgr;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.User;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
OauthProviderVO result = _oauthMgr.updateOauthProvider(this);
|
||||
if (result != null) {
|
||||
OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri());
|
||||
|
||||
List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauthMgr.listUserOAuth2AuthenticationProviders();
|
||||
List<String> authenticatorPluginNames = new ArrayList<>();
|
||||
for (UserOAuth2Authenticator authenticator : userOAuth2AuthenticatorPlugins) {
|
||||
String name = authenticator.getName();
|
||||
authenticatorPluginNames.add(name);
|
||||
}
|
||||
if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
|
||||
r.setEnabled(true);
|
||||
} else {
|
||||
r.setEnabled(false);
|
||||
}
|
||||
|
||||
r.setObjectName(ApiConstants.OAUTH_PROVIDER);
|
||||
r.setResponseName(getCommandName());
|
||||
this.setResponseObject(r);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update OAuth provider");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
// 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.oauth2.api.command;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.user.Account;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseListCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticationType;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticator;
|
||||
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
|
||||
import org.apache.cloudstack.api.response.UserResponse;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
@APICommand(name = "verifyOAuthCodeAndGetUser", description = "Verify the OAuth Code and fetch the corresponding user from provider", responseObject = OauthProviderResponse.class, entityType = {},
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0")
|
||||
public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuthenticator {
|
||||
public static final Logger s_logger = Logger.getLogger(VerifyOAuthCodeAndGetUserCmd.class.getName());
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider", required = true)
|
||||
private String provider;
|
||||
|
||||
@Parameter(name = ApiConstants.SECRET_CODE, type = CommandType.STRING, description = "Code that is provided by OAuth provider (Eg. google, github) after successful login")
|
||||
private String secretCode;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public String getSecretCode() {
|
||||
return secretCode;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
protected OAuth2AuthManager _oauth2mgr;
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return Account.Type.NORMAL.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ServerApiException {
|
||||
// We should never reach here
|
||||
throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb, HttpServletRequest req, HttpServletResponse resp) throws ServerApiException {
|
||||
final String[] secretcodeArray = (String[])params.get(ApiConstants.SECRET_CODE);
|
||||
final String[] providerArray = (String[])params.get(ApiConstants.PROVIDER);
|
||||
if (ArrayUtils.isNotEmpty(secretcodeArray)) {
|
||||
secretCode = secretcodeArray[0];
|
||||
}
|
||||
if (ArrayUtils.isNotEmpty(providerArray)) {
|
||||
provider = providerArray[0];
|
||||
}
|
||||
|
||||
String email = _oauth2mgr.verifyCodeAndFetchEmail(secretCode, provider);
|
||||
if (email != null) {
|
||||
UserResponse response = new UserResponse();
|
||||
response.setEmail(email);
|
||||
response.setResponseName(getCommandName());
|
||||
response.setObjectName("oauthemail");
|
||||
|
||||
return ApiResponseSerializer.toSerializedString(response, responseType);
|
||||
}
|
||||
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unable to verify the code provided");
|
||||
}
|
||||
|
||||
@Override
|
||||
public APIAuthenticationType getAPIType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
|
||||
for (PluggableAPIAuthenticator authManager: authenticators) {
|
||||
if (authManager != null && authManager instanceof OAuth2AuthManager) {
|
||||
_oauth2mgr = (OAuth2AuthManager) authManager;
|
||||
}
|
||||
}
|
||||
if (_oauth2mgr == null) {
|
||||
s_logger.error("No suitable Pluggable Authentication Manager found for listing OAuth providers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
// 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.oauth2.api.response;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
@EntityReference(value = OauthProviderVO.class)
|
||||
public class OauthProviderResponse extends BaseResponse {
|
||||
|
||||
@SerializedName(ApiConstants.ID)
|
||||
@Param(description = "ID of the provider")
|
||||
private String id;
|
||||
|
||||
@SerializedName(ApiConstants.PROVIDER)
|
||||
@Param(description = "Name of the provider")
|
||||
private String provider;
|
||||
|
||||
@SerializedName(ApiConstants.NAME)
|
||||
@Param(description = "Name of the provider")
|
||||
private String name;
|
||||
|
||||
@SerializedName(ApiConstants.DESCRIPTION)
|
||||
@Param(description = "Description of the provider registered")
|
||||
private String description;
|
||||
|
||||
@SerializedName(ApiConstants.CLIENT_ID)
|
||||
@Param(description = "Client ID registered in the OAuth provider")
|
||||
private String clientId;
|
||||
|
||||
@SerializedName(ApiConstants.OAUTH_SECRET_KEY)
|
||||
@Param(description = "Secret key registered in the OAuth provider")
|
||||
private String secretKey;
|
||||
|
||||
@SerializedName(ApiConstants.REDIRECT_URI)
|
||||
@Param(description = "Redirect URI registered in the OAuth provider")
|
||||
private String redirectUri;
|
||||
|
||||
@SerializedName(ApiConstants.ENABLED)
|
||||
@Param(description = "Whether the OAuth provider is enabled or not")
|
||||
private boolean enabled;
|
||||
|
||||
public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri) {
|
||||
this.id = id;
|
||||
this.provider = provider;
|
||||
this.name = provider;
|
||||
this.description = description;
|
||||
this.clientId = clientId;
|
||||
this.secretKey = secretKey;
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public void setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
// 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.oauth2.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
public interface OauthProviderDao extends GenericDao<OauthProviderVO, Long> {
|
||||
|
||||
public OauthProviderVO findByProvider(String provider);
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
// 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.oauth2.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
public class OauthProviderDaoImpl extends GenericDaoBase<OauthProviderVO, Long> implements OauthProviderDao {
|
||||
|
||||
private final SearchBuilder<OauthProviderVO> oauthProviderSearchByName;
|
||||
|
||||
public OauthProviderDaoImpl() {
|
||||
super();
|
||||
|
||||
oauthProviderSearchByName = createSearchBuilder();
|
||||
oauthProviderSearchByName.and("provider", oauthProviderSearchByName.entity().getProvider(), SearchCriteria.Op.EQ);
|
||||
oauthProviderSearchByName.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OauthProviderVO findByProvider(String provider) {
|
||||
SearchCriteria<OauthProviderVO> sc = oauthProviderSearchByName.create();
|
||||
sc.setParameters("provider", provider);
|
||||
|
||||
return findOneBy(sc);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
//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
|
||||
//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.oauth2.github;
|
||||
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class GithubOAuth2Provider extends AdapterBase implements UserOAuth2Authenticator {
|
||||
|
||||
@Inject
|
||||
OauthProviderDao _oauthProviderDao;
|
||||
|
||||
private String accessToken = null;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "github";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Github OAuth2 Provider Plugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode) {
|
||||
if (StringUtils.isAnyEmpty(email, secretCode)) {
|
||||
throw new CloudRuntimeException(String.format("Either email or secretcode should not be null/empty"));
|
||||
}
|
||||
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName());
|
||||
if (providerVO == null) {
|
||||
throw new CloudRuntimeException("Github provider is not registered, so user cannot be verified");
|
||||
}
|
||||
|
||||
String verifiedEmail = getUserEmailAddress();
|
||||
if (verifiedEmail == null || !email.equals(verifiedEmail)) {
|
||||
throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
|
||||
}
|
||||
|
||||
clearAccessToken();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String secretCode) {
|
||||
String accessToken = getAccessToken(secretCode);
|
||||
if (accessToken == null) {
|
||||
return null;
|
||||
}
|
||||
return getUserEmailAddress();
|
||||
}
|
||||
|
||||
protected String getAccessToken(String secretCode) throws CloudRuntimeException {
|
||||
OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName());
|
||||
String tokenUrl = "https://github.com/login/oauth/access_token";
|
||||
String generatedAccessToken = null;
|
||||
try {
|
||||
URL url = new URL(tokenUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
|
||||
String jsonParams = "{\"client_id\":\"" + githubProvider.getClientId() + "\",\"client_secret\":\"" + githubProvider.getSecretKey() + "\",\"code\":\"" + secretCode + "\"}";
|
||||
|
||||
try (OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = jsonParams.getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||
String inputLine;
|
||||
StringBuilder response = new StringBuilder();
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
String regexPattern = "access_token=([^&]+)";
|
||||
Pattern pattern = Pattern.compile(regexPattern);
|
||||
Matcher matcher = pattern.matcher(response);
|
||||
if (matcher.find()) {
|
||||
generatedAccessToken = matcher.group(1);
|
||||
} else {
|
||||
throw new CloudRuntimeException("Could not fetch access token from the given code");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new CloudRuntimeException("HTTP Request while fetching access token from github failed with error code: " + responseCode);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new CloudRuntimeException(String.format("Error while trying to fetch the github access token : %s", e.getMessage()));
|
||||
}
|
||||
|
||||
accessToken = generatedAccessToken;
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getUserEmailAddress() throws CloudRuntimeException {
|
||||
if (accessToken == null) {
|
||||
throw new CloudRuntimeException("Access Token not found to fetch the email address");
|
||||
}
|
||||
|
||||
String apiUrl = "https://api.github.com/user/emails";
|
||||
String email = null;
|
||||
try {
|
||||
URL url = new URL(apiUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Authorization", "token " + accessToken);
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||
String inputLine;
|
||||
StringBuilder response = new StringBuilder();
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
response.append(inputLine);
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode jsonNode = objectMapper.readTree(response.toString());
|
||||
if (jsonNode != null && jsonNode.isArray()) {
|
||||
JsonNode firstObject = jsonNode.get(0);
|
||||
email = firstObject.get("email").asText();
|
||||
} else {
|
||||
throw new CloudRuntimeException("Invalid JSON format found while accessing email from github");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new CloudRuntimeException(String.format("Error occurred while accessing email from github: %s", e.getMessage()));
|
||||
} }
|
||||
} else {
|
||||
throw new CloudRuntimeException(String.format("HTTP Request Failed with error code: %s", responseCode));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new CloudRuntimeException(String.format("Error while trying to fetch email from github : %s", e.getMessage()));
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
private void clearAccessToken() {
|
||||
accessToken = null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
//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
|
||||
//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.oauth2.google;
|
||||
|
||||
import com.cloud.exception.CloudAuthenticationException;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.services.oauth2.Oauth2;
|
||||
import com.google.api.services.oauth2.model.Userinfo;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authenticator {
|
||||
private static final Logger s_logger = Logger.getLogger(GoogleOAuth2Provider.class);
|
||||
|
||||
protected String accessToken = null;
|
||||
protected String refreshToken = null;
|
||||
|
||||
@Inject
|
||||
OauthProviderDao _oauthProviderDao;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "google";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Google OAuth2 Provider Plugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode) {
|
||||
if (StringUtils.isAnyEmpty(email, secretCode)) {
|
||||
throw new CloudAuthenticationException("Either email or secret code should not be null/empty");
|
||||
}
|
||||
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName());
|
||||
if (providerVO == null) {
|
||||
throw new CloudAuthenticationException("Google provider is not registered, so user cannot be verified");
|
||||
}
|
||||
|
||||
String verifiedEmail = verifyCodeAndFetchEmail(secretCode);
|
||||
if (verifiedEmail == null || !email.equals(verifiedEmail)) {
|
||||
throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
|
||||
}
|
||||
clearAccessAndRefreshTokens();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String secretCode) {
|
||||
OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName());
|
||||
String clientId = githubProvider.getClientId();
|
||||
String secret = githubProvider.getSecretKey();
|
||||
String redirectURI = githubProvider.getRedirectUri();
|
||||
GoogleClientSecrets clientSecrets = new GoogleClientSecrets()
|
||||
.setWeb(new GoogleClientSecrets.Details()
|
||||
.setClientId(clientId)
|
||||
.setClientSecret(secret));
|
||||
|
||||
NetHttpTransport httpTransport = new NetHttpTransport();
|
||||
JsonFactory jsonFactory = new JacksonFactory();
|
||||
List<String> scopes = Arrays.asList(
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/userinfo.email");
|
||||
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
|
||||
httpTransport, jsonFactory, clientSecrets, scopes)
|
||||
.build();
|
||||
|
||||
if (StringUtils.isAnyEmpty(accessToken, refreshToken)) {
|
||||
GoogleTokenResponse tokenResponse = null;
|
||||
try {
|
||||
tokenResponse = flow.newTokenRequest(secretCode)
|
||||
.setRedirectUri(redirectURI)
|
||||
.execute();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
accessToken = tokenResponse.getAccessToken();
|
||||
refreshToken = tokenResponse.getRefreshToken();
|
||||
}
|
||||
|
||||
GoogleCredential credential = new GoogleCredential.Builder()
|
||||
.setTransport(httpTransport)
|
||||
.setJsonFactory(jsonFactory)
|
||||
.setClientSecrets(clientSecrets)
|
||||
.build()
|
||||
.setAccessToken(accessToken)
|
||||
.setRefreshToken(refreshToken);
|
||||
|
||||
Oauth2 oauth2 = new Oauth2.Builder(httpTransport, jsonFactory, credential).build();
|
||||
Userinfo userinfo = null;
|
||||
try {
|
||||
userinfo = oauth2.userinfo().get().execute();
|
||||
} catch (IOException e) {
|
||||
throw new CloudRuntimeException(String.format("Failed to fetch the email address with the provided secret: %s" + e.getMessage()));
|
||||
}
|
||||
return userinfo.getEmail();
|
||||
}
|
||||
|
||||
protected void clearAccessAndRefreshTokens() {
|
||||
accessToken = null;
|
||||
refreshToken = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserEmailAddress() throws CloudRuntimeException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
// 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.oauth2.vo;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "oauth_provider")
|
||||
public class OauthProviderVO implements Identity, InternalIdentity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "uuid")
|
||||
private String uuid;
|
||||
|
||||
@Column(name = "description")
|
||||
private String description;
|
||||
|
||||
@Column(name = "provider")
|
||||
private String provider;
|
||||
|
||||
@Column(name = "client_id")
|
||||
private String clientId;
|
||||
|
||||
@Column(name = "secret_key")
|
||||
private String secretKey;
|
||||
|
||||
@Column(name = "redirect_uri")
|
||||
private String redirectUri;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN)
|
||||
private Date created;
|
||||
|
||||
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||
private Date removed;
|
||||
|
||||
@Column(name = "enabled")
|
||||
private boolean enabled = true;
|
||||
|
||||
public OauthProviderVO () {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public void setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
public void setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@ -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=oauth2
|
||||
parent=api
|
||||
@ -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.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<bean id="OauthProviderDao" class="org.apache.cloudstack.oauth2.dao.OauthProviderDaoImpl" />
|
||||
<bean id="OAuth2UserAuthenticator" class="org.apache.cloudstack.oauth2.OAuth2UserAuthenticator">
|
||||
<property name="name" value="oauth2"/>
|
||||
</bean>
|
||||
<bean id="GoogleOAuth2Provider" class="org.apache.cloudstack.oauth2.google.GoogleOAuth2Provider">
|
||||
<property name="name" value="google" />
|
||||
</bean>
|
||||
<bean id="GithubOAuth2Provider" class="org.apache.cloudstack.oauth2.github.GithubOAuth2Provider">
|
||||
<property name="name" value="github" />
|
||||
</bean>
|
||||
|
||||
<bean id="OAuth2AuthManager" class="org.apache.cloudstack.oauth2.OAuth2AuthManagerImpl">
|
||||
<property name="name" value="OAUTH2Auth" />
|
||||
<property name="userOAuth2AuthenticationProviders" value="#{userOAuth2AuthenticatorsRegistry.registered}" />
|
||||
</bean>
|
||||
|
||||
<bean id="userOAuth2AuthenticatorsRegistry"
|
||||
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
<property name="orderConfigKey" value="user.oauth2.providers.order" />
|
||||
<property name="excludeKey" value="oauth2.plugins.exclude" />
|
||||
<property name="orderConfigDefault" value="google,github" />
|
||||
</bean>
|
||||
|
||||
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
|
||||
<property name="registry" ref="userOAuth2AuthenticatorsRegistry" />
|
||||
<property name="typeClass"
|
||||
value="org.apache.cloudstack.auth.UserOAuth2Authenticator" />
|
||||
</bean>
|
||||
</beans>
|
||||
@ -0,0 +1,191 @@
|
||||
//
|
||||
// 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.oauth2;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.oauth2.api.command.DeleteOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.RegisterOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class OAuth2AuthManagerImplTest {
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private OAuth2AuthManagerImpl _authManager;
|
||||
|
||||
@Mock
|
||||
OauthProviderDao _oauthProviderDao;
|
||||
|
||||
AutoCloseable closeable;
|
||||
@Before
|
||||
public void setUp() {
|
||||
closeable = MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterOauthProvider() {
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(false);
|
||||
RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
try {
|
||||
_authManager.registerOauthProvider(cmd);
|
||||
Assert.fail("Expected CloudRuntimeException was not thrown");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertEquals("OAuth is not enabled, please enable to register", e.getMessage());
|
||||
}
|
||||
|
||||
// Test when provider is already registered
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(true);
|
||||
OauthProviderVO providerVO = new OauthProviderVO();
|
||||
providerVO.setProvider("testProvider");
|
||||
when(_authManager._oauthProviderDao.findByProvider(Mockito.anyString())).thenReturn(providerVO);
|
||||
when(cmd.getProvider()).thenReturn("testProvider");
|
||||
|
||||
try {
|
||||
_authManager.registerOauthProvider(cmd);
|
||||
Assert.fail("Expected CloudRuntimeException was not thrown");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertEquals("Provider with the name testProvider is already registered", e.getMessage());
|
||||
}
|
||||
|
||||
// Test when provider is github and secret key is not null
|
||||
when(cmd.getSecretKey()).thenReturn("testSecretKey");
|
||||
providerVO = null;
|
||||
when(_authManager._oauthProviderDao.findByProvider(Mockito.anyString())).thenReturn(providerVO);
|
||||
OauthProviderVO savedProviderVO = new OauthProviderVO();
|
||||
when(cmd.getProvider()).thenReturn("github");
|
||||
when(_authManager._oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenReturn(savedProviderVO);
|
||||
OauthProviderVO result = _authManager.registerOauthProvider(cmd);
|
||||
assertEquals("github", result.getProvider());
|
||||
assertEquals("testSecretKey", result.getSecretKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOauthProvider() {
|
||||
Long id = 1L;
|
||||
String description = "updated description";
|
||||
String clientId = "updated client id";
|
||||
String redirectUri = "updated redirect uri";
|
||||
String secretKey = "updated secret key";
|
||||
|
||||
UpdateOAuthProviderCmd cmd = Mockito.mock(UpdateOAuthProviderCmd.class);
|
||||
when(cmd.getId()).thenReturn(id);
|
||||
when(cmd.getDescription()).thenReturn(description);
|
||||
when(cmd.getClientId()).thenReturn(clientId);
|
||||
when(cmd.getRedirectUri()).thenReturn(redirectUri);
|
||||
when(cmd.getSecretKey()).thenReturn(secretKey);
|
||||
|
||||
OauthProviderVO providerVO = new OauthProviderVO();
|
||||
providerVO.setDescription("old description");
|
||||
providerVO.setClientId("old client id");
|
||||
providerVO.setRedirectUri("old redirect uri");
|
||||
providerVO.setSecretKey("old secret key");
|
||||
|
||||
when(_oauthProviderDao.findById(id)).thenReturn(providerVO);
|
||||
|
||||
OauthProviderVO updatedProviderVO = new OauthProviderVO();
|
||||
updatedProviderVO.setDescription(description);
|
||||
updatedProviderVO.setClientId(clientId);
|
||||
updatedProviderVO.setRedirectUri(redirectUri);
|
||||
updatedProviderVO.setSecretKey(secretKey);
|
||||
|
||||
when(_oauthProviderDao.update(id, providerVO)).thenReturn(true);
|
||||
|
||||
OauthProviderVO result = _authManager.updateOauthProvider(cmd);
|
||||
|
||||
assertEquals(description, result.getDescription());
|
||||
assertEquals(clientId, result.getClientId());
|
||||
assertEquals(redirectUri, result.getRedirectUri());
|
||||
assertEquals(secretKey, result.getSecretKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListOauthProviders() {
|
||||
String uuid = "1234-5678-9101";
|
||||
String provider = "testProvider";
|
||||
OauthProviderVO providerVO = new OauthProviderVO();
|
||||
providerVO.setProvider(provider);
|
||||
List<OauthProviderVO> providerList = Collections.singletonList(providerVO);
|
||||
|
||||
// Test when uuid is not null
|
||||
when(_oauthProviderDao.findByUuid(uuid)).thenReturn(providerVO);
|
||||
List<OauthProviderVO> result = _authManager.listOauthProviders(null, uuid);
|
||||
assertEquals(providerList, result);
|
||||
|
||||
// Test when provider is not blank
|
||||
when(_oauthProviderDao.findByProvider(provider)).thenReturn(providerVO);
|
||||
result = _authManager.listOauthProviders(provider, null);
|
||||
assertEquals(providerList, result);
|
||||
|
||||
// Test when both uuid and provider are null
|
||||
when(_oauthProviderDao.listAll()).thenReturn(providerList);
|
||||
result = _authManager.listOauthProviders(null, null);
|
||||
assertEquals(providerList, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommands() {
|
||||
List<Class<?>> expectedCmdList = new ArrayList<>();
|
||||
expectedCmdList.add(RegisterOAuthProviderCmd.class);
|
||||
expectedCmdList.add(DeleteOAuthProviderCmd.class);
|
||||
expectedCmdList.add(UpdateOAuthProviderCmd.class);
|
||||
|
||||
List<Class<?>> cmdList = _authManager.getCommands();
|
||||
|
||||
assertEquals(expectedCmdList, cmdList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart() {
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(true);
|
||||
doNothing().when(_authManager).initializeUserOAuth2AuthenticationProvidersMap();
|
||||
boolean result = _authManager.start();
|
||||
assertTrue(result);
|
||||
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(false);
|
||||
result = _authManager.start();
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
//
|
||||
// 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.oauth2;
|
||||
|
||||
import com.cloud.user.UserAccount;
|
||||
import com.cloud.user.UserVO;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.user.dao.UserDao;
|
||||
import com.cloud.utils.Pair;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class OAuth2UserAuthenticatorTest {
|
||||
|
||||
@Mock
|
||||
private UserAccountDao userAccountDao;
|
||||
|
||||
@Mock
|
||||
private UserDao userDao;
|
||||
|
||||
@Mock
|
||||
private OAuth2AuthManager userOAuth2mgr;
|
||||
|
||||
@InjectMocks
|
||||
private OAuth2UserAuthenticator authenticator;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateWithValidCredentials() {
|
||||
String username = "testuser";
|
||||
Long domainId = 1L;
|
||||
String[] provider = {"testprovider"};
|
||||
String[] email = {"testemail"};
|
||||
String[] secretCode = {"testsecretcode"};
|
||||
|
||||
UserAccount userAccount = mock(UserAccount.class);
|
||||
UserVO user = mock(UserVO.class);
|
||||
UserOAuth2Authenticator userOAuth2Authenticator = mock(UserOAuth2Authenticator.class);
|
||||
|
||||
when(userAccountDao.getUserAccount(username, domainId)).thenReturn(userAccount);
|
||||
when(userDao.getUser(userAccount.getId())).thenReturn(user);
|
||||
when(userOAuth2mgr.getUserOAuth2AuthenticationProvider(provider[0])).thenReturn(userOAuth2Authenticator);
|
||||
when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0])).thenReturn(true);
|
||||
|
||||
Map<String, Object[]> requestParameters = new HashMap<>();
|
||||
requestParameters.put("provider", provider);
|
||||
requestParameters.put("email", email);
|
||||
requestParameters.put("secretcode", secretCode);
|
||||
|
||||
Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result = authenticator.authenticate(username, null, domainId, requestParameters);
|
||||
|
||||
verify(userAccountDao).getUserAccount(username, domainId);
|
||||
verify(userDao).getUser(userAccount.getId());
|
||||
verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]);
|
||||
verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]);
|
||||
|
||||
assertEquals(true, result.first().booleanValue());
|
||||
assertEquals(null, result.second());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateWithInvalidCredentials() {
|
||||
String username = "testuser";
|
||||
Long domainId = 1L;
|
||||
String[] provider = {"testprovider"};
|
||||
String[] email = {"testemail"};
|
||||
String[] secretCode = {"testsecretcode"};
|
||||
|
||||
UserAccount userAccount = mock(UserAccount.class);
|
||||
UserVO user = mock(UserVO.class);
|
||||
UserOAuth2Authenticator userOAuth2Authenticator = mock(UserOAuth2Authenticator.class);
|
||||
|
||||
when(userAccountDao.getUserAccount(username, domainId)).thenReturn(userAccount);
|
||||
when(userDao.getUser(userAccount.getId())).thenReturn( user);
|
||||
when(userOAuth2mgr.getUserOAuth2AuthenticationProvider(provider[0])).thenReturn(userOAuth2Authenticator);
|
||||
when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0])).thenReturn(false);
|
||||
|
||||
Map<String, Object[]> requestParameters = new HashMap<>();
|
||||
requestParameters.put("provider", provider);
|
||||
requestParameters.put("email", email);
|
||||
requestParameters.put("secretcode", secretCode);
|
||||
|
||||
Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result = authenticator.authenticate(username, null, domainId, requestParameters);
|
||||
|
||||
verify(userAccountDao).getUserAccount(username, domainId);
|
||||
verify(userDao).getUser(userAccount.getId());
|
||||
verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]);
|
||||
verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]);
|
||||
|
||||
assertEquals(false, result.first().booleanValue());
|
||||
assertEquals(OAuth2UserAuthenticator.ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT, result.second());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateWithInvalidUserAccount() {
|
||||
String username = "testuser";
|
||||
Long domainId = 1L;
|
||||
String[] provider = {"testprovider"};
|
||||
String[] email = {"testemail"};
|
||||
String[] secretCode = {"testsecretcode"};
|
||||
|
||||
when(userAccountDao.getUserAccount(username, domainId)).thenReturn(null);
|
||||
|
||||
Map<String, Object[]> requestParameters = new HashMap<>();
|
||||
requestParameters.put("provider", provider);
|
||||
requestParameters.put("email", email);
|
||||
requestParameters.put("secretcode", secretCode);
|
||||
|
||||
Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result = authenticator.authenticate(username, null, domainId, requestParameters);
|
||||
|
||||
verify(userAccountDao).getUserAccount(username, domainId);
|
||||
verify(userDao, never()).getUser(anyLong());
|
||||
verify(userOAuth2mgr, never()).getUserOAuth2AuthenticationProvider(anyString());
|
||||
|
||||
assertEquals(false, result.first().booleanValue());
|
||||
assertEquals(null, result.second());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.oauth2.api.command;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DeleteOAuthProviderCmdTest {
|
||||
|
||||
@Mock
|
||||
private OAuth2AuthManager _oauthMgr;
|
||||
|
||||
@InjectMocks
|
||||
private DeleteOAuthProviderCmd cmd;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test(expected = ServerApiException.class)
|
||||
public void testExecuteFailure() {
|
||||
when(_oauthMgr.deleteOauthProvider(cmd.getId())).thenReturn(false);
|
||||
cmd.execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteSuccess() {
|
||||
when(_oauthMgr.deleteOauthProvider(cmd.getId())).thenReturn(true);
|
||||
cmd.execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetApiResourceType() {
|
||||
assert (cmd.getApiResourceType() == org.apache.cloudstack.api.ApiCommandResourceType.User);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteOAuthProvider() {
|
||||
when(_oauthMgr.deleteOauthProvider(null)).thenReturn(true);
|
||||
cmd.execute();
|
||||
|
||||
assertTrue(cmd.getResponseObject() instanceof SuccessResponse);
|
||||
}
|
||||
|
||||
@Test(expected = ServerApiException.class)
|
||||
public void testDeleteOAuthProviderExpectFailure() {
|
||||
when(_oauthMgr.deleteOauthProvider(null)).thenReturn(false);
|
||||
cmd.execute();
|
||||
}
|
||||
}
|
||||
@ -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.oauth2.api.command;
|
||||
|
||||
import com.cloud.api.ApiServer;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class OauthLoginAPIAuthenticatorCmdTest {
|
||||
@InjectMocks
|
||||
private OauthLoginAPIAuthenticatorCmd cmd;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
@Test
|
||||
public void testGetDomainNameWhenDomainNameIsNull() {
|
||||
StringBuilder auditTrailSb = new StringBuilder();
|
||||
String[] domainName = null;
|
||||
String domain = cmd.getDomainName(auditTrailSb, domainName);
|
||||
assertNull(domain);
|
||||
assertEquals("", auditTrailSb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDomainNameWithStartingSlash() {
|
||||
StringBuilder auditTrailSb = new StringBuilder();
|
||||
String[] domainName = {"/example"};
|
||||
String domain = cmd.getDomainName(auditTrailSb, domainName);
|
||||
assertEquals("/example/", domain);
|
||||
assertEquals(" domain=/example", auditTrailSb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDomainNameWithEndingSlash() {
|
||||
StringBuilder auditTrailSb = new StringBuilder();
|
||||
String[] domainName = {"example/"};
|
||||
String domain = cmd.getDomainName(auditTrailSb, domainName);
|
||||
assertEquals("/example/", domain);
|
||||
assertEquals(" domain=example/", auditTrailSb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDomainIdFromParams() {
|
||||
StringBuilder auditTrailSb = new StringBuilder();
|
||||
String responseType = "json";
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN_ID, new String[]{"1234"});
|
||||
ApiServer apiServer = mock(ApiServer.class);
|
||||
cmd._apiServer = apiServer;
|
||||
when(apiServer.fetchDomainId("1234")).thenReturn(5678L);
|
||||
|
||||
Long domainId = cmd.getDomainIdFromParams(params, auditTrailSb, responseType);
|
||||
|
||||
assertEquals(Long.valueOf(5678), domainId);
|
||||
assertEquals(" domainid=5678", auditTrailSb.toString());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.oauth2.api.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RegisterOAuthProviderCmdTest {
|
||||
|
||||
@Mock
|
||||
private OAuth2AuthManager _oauth2mgr;
|
||||
|
||||
@InjectMocks
|
||||
private RegisterOAuthProviderCmd _cmd;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute() throws ServerApiException {
|
||||
OauthProviderVO provider = mock(OauthProviderVO.class);
|
||||
when(_oauth2mgr.registerOauthProvider(_cmd)).thenReturn(provider);
|
||||
|
||||
_cmd.execute();
|
||||
assertEquals(ApiConstants.OAUTH_PROVIDER, ((OauthProviderResponse)_cmd.getResponseObject()).getObjectName());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.oauth2.api.command;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class VerifyOAuthCodeAndGetUserCmdTest {
|
||||
|
||||
private VerifyOAuthCodeAndGetUserCmd cmd;
|
||||
private OAuth2AuthManager oauth2mgr;
|
||||
private HttpSession session;
|
||||
private InetAddress remoteAddress;
|
||||
private StringBuilder auditTrailSb;
|
||||
private HttpServletRequest req;
|
||||
private HttpServletResponse resp;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cmd = new VerifyOAuthCodeAndGetUserCmd();
|
||||
oauth2mgr = mock(OAuth2AuthManager.class);
|
||||
session = mock(HttpSession.class);
|
||||
remoteAddress = mock(InetAddress.class);
|
||||
auditTrailSb = new StringBuilder();
|
||||
req = mock(HttpServletRequest.class);
|
||||
resp = mock(HttpServletResponse.class);
|
||||
cmd._oauth2mgr = oauth2mgr;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate() {
|
||||
final String[] secretcodeArray = new String[] { "secretcode" };
|
||||
final String[] providerArray = new String[] { "provider" };
|
||||
final String responseType = "json";
|
||||
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put("secretcode", secretcodeArray);
|
||||
params.put("provider", providerArray);
|
||||
|
||||
when(oauth2mgr.verifyCodeAndFetchEmail("secretcode", "provider")).thenReturn("test@example.com");
|
||||
|
||||
String response = cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp);
|
||||
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertTrue(response.contains("test@example.com"));
|
||||
}
|
||||
|
||||
@Test(expected = ServerApiException.class)
|
||||
public void testAuthenticateWithInvalidCode() throws Exception {
|
||||
final String[] secretcodeArray = new String[] { "invalidcode" };
|
||||
final String[] providerArray = new String[] { "provider" };
|
||||
final String responseType = "json";
|
||||
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put("secretcode", secretcodeArray);
|
||||
params.put("provider", providerArray);
|
||||
|
||||
when(oauth2mgr.verifyCodeAndFetchEmail("invalidcode", "provider")).thenReturn(null);
|
||||
|
||||
cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAuthenticators() {
|
||||
VerifyOAuthCodeAndGetUserCmd cmd = new VerifyOAuthCodeAndGetUserCmd();
|
||||
OAuth2AuthManager oauth2mgr = mock(OAuth2AuthManager.class);
|
||||
List<PluggableAPIAuthenticator> authenticators = new ArrayList<>();
|
||||
authenticators.add(mock(PluggableAPIAuthenticator.class));
|
||||
authenticators.add(oauth2mgr);
|
||||
authenticators.add(null);
|
||||
cmd.setAuthenticators(authenticators);
|
||||
Assert.assertEquals(oauth2mgr, cmd._oauth2mgr);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,148 @@
|
||||
//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
|
||||
//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.oauth2.google;
|
||||
|
||||
import com.cloud.exception.CloudAuthenticationException;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.google.api.services.oauth2.Oauth2;
|
||||
import com.google.api.services.oauth2.model.Userinfo;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedConstruction;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class GoogleOAuth2ProviderTest {
|
||||
|
||||
@Mock
|
||||
private OauthProviderDao _oauthProviderDao;
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private GoogleOAuth2Provider _googleOAuth2Provider;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test(expected = CloudAuthenticationException.class)
|
||||
public void testVerifyUserWithNullEmail() {
|
||||
_googleOAuth2Provider.verifyUser(null, "secretCode");
|
||||
}
|
||||
|
||||
@Test(expected = CloudAuthenticationException.class)
|
||||
public void testVerifyUserWithNullSecretCode() {
|
||||
_googleOAuth2Provider.verifyUser("email@example.com", null);
|
||||
}
|
||||
|
||||
@Test(expected = CloudAuthenticationException.class)
|
||||
public void testVerifyUserWithUnregisteredProvider() {
|
||||
when(_oauthProviderDao.findByProvider(anyString())).thenReturn(null);
|
||||
_googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyUserWithInvalidSecretCode() throws IOException {
|
||||
OauthProviderVO providerVO = mock(OauthProviderVO.class);
|
||||
when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
|
||||
when(providerVO.getProvider()).thenReturn("testProvider");
|
||||
when(providerVO.getSecretKey()).thenReturn("testSecret");
|
||||
when(providerVO.getClientId()).thenReturn("testClientid");
|
||||
_googleOAuth2Provider.accessToken = "testAccessToken";
|
||||
_googleOAuth2Provider.refreshToken = "testRefreshToken";
|
||||
Oauth2 oauth2 = mock(Oauth2.class);
|
||||
try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
|
||||
(mock, context) -> when(mock.build()).thenReturn(oauth2))) {
|
||||
Userinfo userinfo = mock(Userinfo.class);
|
||||
Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
|
||||
when(oauth2.userinfo()).thenReturn(userinfo1);
|
||||
Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
|
||||
when(userinfo1.get()).thenReturn(userinfoGet);
|
||||
when(userinfoGet.execute()).thenReturn(userinfo);
|
||||
when(userinfo.getEmail()).thenReturn(null);
|
||||
|
||||
_googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyUserWithMismatchedEmail() throws IOException {
|
||||
OauthProviderVO providerVO = mock(OauthProviderVO.class);
|
||||
when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
|
||||
when(providerVO.getProvider()).thenReturn("testProvider");
|
||||
when(providerVO.getSecretKey()).thenReturn("testSecret");
|
||||
when(providerVO.getClientId()).thenReturn("testClientid");
|
||||
_googleOAuth2Provider.accessToken = "testAccessToken";
|
||||
_googleOAuth2Provider.refreshToken = "testRefreshToken";
|
||||
Oauth2 oauth2 = mock(Oauth2.class);
|
||||
try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
|
||||
(mock, context) -> when(mock.build()).thenReturn(oauth2))) {
|
||||
Userinfo userinfo = mock(Userinfo.class);
|
||||
Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
|
||||
when(oauth2.userinfo()).thenReturn(userinfo1);
|
||||
Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
|
||||
when(userinfo1.get()).thenReturn(userinfoGet);
|
||||
when(userinfoGet.execute()).thenReturn(userinfo);
|
||||
when(userinfo.getEmail()).thenReturn("otheremail@example.com");
|
||||
|
||||
_googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyUserEmail() throws IOException {
|
||||
OauthProviderVO providerVO = mock(OauthProviderVO.class);
|
||||
when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
|
||||
when(providerVO.getProvider()).thenReturn("testProvider");
|
||||
when(providerVO.getSecretKey()).thenReturn("testSecret");
|
||||
when(providerVO.getClientId()).thenReturn("testClientid");
|
||||
_googleOAuth2Provider.accessToken = "testAccessToken";
|
||||
_googleOAuth2Provider.refreshToken = "testRefreshToken";
|
||||
Oauth2 oauth2 = mock(Oauth2.class);
|
||||
try (MockedConstruction<Oauth2.Builder> ignored = Mockito.mockConstruction(Oauth2.Builder.class,
|
||||
(mock, context) -> when(mock.build()).thenReturn(oauth2))) {
|
||||
Userinfo userinfo = mock(Userinfo.class);
|
||||
Oauth2.Userinfo userinfo1 = mock(Oauth2.Userinfo.class);
|
||||
when(oauth2.userinfo()).thenReturn(userinfo1);
|
||||
Oauth2.Userinfo.Get userinfoGet = mock(Oauth2.Userinfo.Get.class);
|
||||
when(userinfo1.get()).thenReturn(userinfoGet);
|
||||
when(userinfoGet.execute()).thenReturn(userinfo);
|
||||
when(userinfo.getEmail()).thenReturn("email@example.com");
|
||||
|
||||
boolean result = _googleOAuth2Provider.verifyUser("email@example.com", "secretCode");
|
||||
|
||||
assertTrue(result);
|
||||
assertNull(_googleOAuth2Provider.accessToken);
|
||||
assertNull(_googleOAuth2Provider.refreshToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,6 @@ public class APIAuthenticationManagerImpl extends ManagerBase implements APIAuth
|
||||
cmdList.add(ValidateUserTwoFactorAuthenticationCodeCmd.class);
|
||||
cmdList.add(SetupUserTwoFactorAuthenticationCmd.class);
|
||||
|
||||
|
||||
for (PluggableAPIAuthenticator apiAuthenticator: _apiAuthenticators) {
|
||||
List<Class<?>> commands = apiAuthenticator.getAuthCommands();
|
||||
if (commands != null) {
|
||||
|
||||
@ -32,6 +32,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
@ -52,6 +53,7 @@ import org.apache.cloudstack.affinity.AffinityGroup;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
|
||||
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
|
||||
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
|
||||
@ -2329,6 +2331,15 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
return _userAccountDao.getUserAccount(username, domainId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId) {
|
||||
List<UserAccountVO> userAccountByEmail = _userAccountDao.getUserAccountByEmail(email, domainId);
|
||||
List<UserAccount> userAccounts = userAccountByEmail.stream()
|
||||
.map(userAccountVO -> (UserAccount) userAccountVO)
|
||||
.collect(Collectors.toList());
|
||||
return userAccounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account getActiveAccountById(long accountId) {
|
||||
return _accountDao.findById(accountId);
|
||||
@ -2473,7 +2484,13 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
@Override
|
||||
public UserAccount authenticateUser(final String username, final String password, final Long domainId, final InetAddress loginIpAddress, final Map<String, Object[]> requestParameters) {
|
||||
UserAccount user = null;
|
||||
if (password != null && !password.isEmpty()) {
|
||||
final String[] oAuthProviderArray = (String[])requestParameters.get(ApiConstants.PROVIDER);
|
||||
final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE);
|
||||
String oauthProvider = ((oAuthProviderArray == null) ? null : oAuthProviderArray[0]);
|
||||
String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
|
||||
|
||||
|
||||
if ((password != null && !password.isEmpty()) || (oauthProvider != null && secretCode != null)) {
|
||||
user = getUserAccount(username, password, domainId, requestParameters);
|
||||
} else {
|
||||
String key = _configDao.getValue("security.singlesignon.key");
|
||||
@ -2626,11 +2643,16 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
HashSet<ActionOnFailedAuthentication> actionsOnFailedAuthenticaion = new HashSet<ActionOnFailedAuthentication>();
|
||||
User.Source userSource = userAccount != null ? userAccount.getSource() : User.Source.UNKNOWN;
|
||||
for (UserAuthenticator authenticator : _userAuthenticators) {
|
||||
if (userSource != User.Source.UNKNOWN) {
|
||||
final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE);
|
||||
String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
|
||||
if (userSource != User.Source.UNKNOWN && secretCode == null) {
|
||||
if (!authenticator.getName().equalsIgnoreCase(userSource.name())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (secretCode != null && !authenticator.getName().equals("oauth2")) {
|
||||
continue;
|
||||
}
|
||||
Pair<Boolean, ActionOnFailedAuthentication> result = authenticator.authenticate(username, password, domainId, requestParameters);
|
||||
if (result.first()) {
|
||||
authenticated = true;
|
||||
|
||||
@ -200,24 +200,24 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
|
||||
userAccountVO.setSource(User.Source.UNKNOWN);
|
||||
userAccountVO.setState(Account.State.DISABLED.toString());
|
||||
Mockito.when(userAccountDaoMock.getUserAccount("test", 1L)).thenReturn(userAccountVO);
|
||||
Mockito.when(userAuthenticator.authenticate("test", "fail", 1L, null)).thenReturn(failureAuthenticationPair);
|
||||
Mockito.lenient().when(userAuthenticator.authenticate("test", null, 1L, null)).thenReturn(successAuthenticationPair);
|
||||
Mockito.lenient().when(userAuthenticator.authenticate("test", "", 1L, null)).thenReturn(successAuthenticationPair);
|
||||
Mockito.when(userAuthenticator.authenticate("test", "fail", 1L, new HashMap<>())).thenReturn(failureAuthenticationPair);
|
||||
Mockito.lenient().when(userAuthenticator.authenticate("test", null, 1L, new HashMap<>())).thenReturn(successAuthenticationPair);
|
||||
Mockito.lenient().when(userAuthenticator.authenticate("test", "", 1L, new HashMap<>())).thenReturn(successAuthenticationPair);
|
||||
|
||||
//Test for incorrect password. authentication should fail
|
||||
UserAccount userAccount = accountManagerImpl.authenticateUser("test", "fail", 1L, InetAddress.getByName("127.0.0.1"), null);
|
||||
UserAccount userAccount = accountManagerImpl.authenticateUser("test", "fail", 1L, InetAddress.getByName("127.0.0.1"), new HashMap<>());
|
||||
Assert.assertNull(userAccount);
|
||||
|
||||
//Test for null password. authentication should fail
|
||||
userAccount = accountManagerImpl.authenticateUser("test", null, 1L, InetAddress.getByName("127.0.0.1"), null);
|
||||
userAccount = accountManagerImpl.authenticateUser("test", null, 1L, InetAddress.getByName("127.0.0.1"), new HashMap<>());
|
||||
Assert.assertNull(userAccount);
|
||||
|
||||
//Test for empty password. authentication should fail
|
||||
userAccount = accountManagerImpl.authenticateUser("test", "", 1L, InetAddress.getByName("127.0.0.1"), null);
|
||||
userAccount = accountManagerImpl.authenticateUser("test", "", 1L, InetAddress.getByName("127.0.0.1"), new HashMap<>());
|
||||
Assert.assertNull(userAccount);
|
||||
|
||||
//Verifying that the authentication method is only called when password is specified
|
||||
Mockito.verify(userAuthenticator, Mockito.times(1)).authenticate("test", "fail", 1L, null);
|
||||
Mockito.verify(userAuthenticator, Mockito.times(1)).authenticate("test", "fail", 1L, new HashMap<>());
|
||||
Mockito.verify(userAuthenticator, Mockito.never()).authenticate("test", null, 1L, null);
|
||||
Mockito.verify(userAuthenticator, Mockito.never()).authenticate("test", "", 1L, null);
|
||||
}
|
||||
@ -974,4 +974,17 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
|
||||
|
||||
Assert.assertEquals("345543", response.getSecretCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetActiveUserAccountByEmail() {
|
||||
String email = "test@example.com";
|
||||
Long domainId = 1L;
|
||||
List<UserAccountVO> userAccountVOList = new ArrayList<>();
|
||||
UserAccountVO userAccountVO = new UserAccountVO();
|
||||
userAccountVOList.add(userAccountVO);
|
||||
Mockito.when(userAccountDaoMock.getUserAccountByEmail(email, domainId)).thenReturn(userAccountVOList);
|
||||
List<UserAccount> userAccounts = accountManagerImpl.getActiveUserAccountByEmail(email, domainId);
|
||||
Assert.assertEquals(userAccountVOList.size(), userAccounts.size());
|
||||
Assert.assertEquals(userAccountVOList.get(0), userAccounts.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +176,11 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserAccount> getActiveUserAccountByEmail(String email, Long domainId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account getActiveAccountById(long accountId) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
@ -161,6 +161,11 @@ known_categories = {
|
||||
'listIdps': 'Authentication',
|
||||
'authorizeSamlSso': 'Authentication',
|
||||
'listSamlAuthorization': 'Authentication',
|
||||
'oauthlogin': 'Authentication',
|
||||
'deleteOauthProvider': 'Oauth',
|
||||
'listOauthProvider': 'Oauth',
|
||||
'registerOauthProvider': 'Oauth',
|
||||
'updateOauthProvider': 'Oauth',
|
||||
'quota': 'Quota',
|
||||
'emailTemplate': 'Quota',
|
||||
'Capacity': 'System Capacity',
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"antd": "^4.21.4",
|
||||
"antd-theme-webpack-plugin": "^1.3.9",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.21.4",
|
||||
"babel-plugin-require-context-hook": "^1.0.0",
|
||||
"chart.js": "^3.7.1",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
@ -67,9 +67,11 @@
|
||||
"vue-loader": "^16.2.0",
|
||||
"vue-qrious": "^3.1.0",
|
||||
"vue-router": "^4.0.14",
|
||||
"vue-social-auth": "^1.4.9",
|
||||
"vue-uuid": "^3.0.0",
|
||||
"vue-web-storage": "^6.1.0",
|
||||
"vue3-clipboard": "^1.0.0",
|
||||
"vue3-google-login": "^2.0.20",
|
||||
"vuedraggable": "^4.0.3",
|
||||
"vuex": "^4.0.0-0"
|
||||
},
|
||||
|
||||
1
ui/public/assets/github.svg
Normal file
1
ui/public/assets/github.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="32px" height="32px"> <path d="M17.791,46.836C18.502,46.53,19,45.823,19,45v-5.4c0-0.197,0.016-0.402,0.041-0.61C19.027,38.994,19.014,38.997,19,39 c0,0-3,0-3.6,0c-1.5,0-2.8-0.6-3.4-1.8c-0.7-1.3-1-3.5-2.8-4.7C8.9,32.3,9.1,32,9.7,32c0.6,0.1,1.9,0.9,2.7,2c0.9,1.1,1.8,2,3.4,2 c2.487,0,3.82-0.125,4.622-0.555C21.356,34.056,22.649,33,24,33v-0.025c-5.668-0.182-9.289-2.066-10.975-4.975 c-3.665,0.042-6.856,0.405-8.677,0.707c-0.058-0.327-0.108-0.656-0.151-0.987c1.797-0.296,4.843-0.647,8.345-0.714 c-0.112-0.276-0.209-0.559-0.291-0.849c-3.511-0.178-6.541-0.039-8.187,0.097c-0.02-0.332-0.047-0.663-0.051-0.999 c1.649-0.135,4.597-0.27,8.018-0.111c-0.079-0.5-0.13-1.011-0.13-1.543c0-1.7,0.6-3.5,1.7-5c-0.5-1.7-1.2-5.3,0.2-6.6 c2.7,0,4.6,1.3,5.5,2.1C21,13.4,22.9,13,25,13s4,0.4,5.6,1.1c0.9-0.8,2.8-2.1,5.5-2.1c1.5,1.4,0.7,5,0.2,6.6c1.1,1.5,1.7,3.2,1.6,5 c0,0.484-0.045,0.951-0.11,1.409c3.499-0.172,6.527-0.034,8.204,0.102c-0.002,0.337-0.033,0.666-0.051,0.999 c-1.671-0.138-4.775-0.28-8.359-0.089c-0.089,0.336-0.197,0.663-0.325,0.98c3.546,0.046,6.665,0.389,8.548,0.689 c-0.043,0.332-0.093,0.661-0.151,0.987c-1.912-0.306-5.171-0.664-8.879-0.682C35.112,30.873,31.557,32.75,26,32.969V33 c2.6,0,5,3.9,5,6.6V45c0,0.823,0.498,1.53,1.209,1.836C41.37,43.804,48,35.164,48,25C48,12.318,37.683,2,25,2S2,12.318,2,25 C2,35.164,8.63,43.804,17.791,46.836z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
ui/public/assets/google.svg
Normal file
1
ui/public/assets/google.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="100px" height="100px"><path fill="#fbc02d" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12 s5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24s8.955,20,20,20 s20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"/><path fill="#e53935" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039 l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"/><path fill="#4caf50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36 c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"/><path fill="#1565c0" d="M43.611,20.083L43.595,20L42,20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571 c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"/></svg>
|
||||
|
After Width: | Height: | Size: 980 B |
@ -81,6 +81,7 @@
|
||||
"label.action.delete.network.static.route": "Remove Tungsten Fabric network static route",
|
||||
"label.action.delete.network.permission": "Delete network permission",
|
||||
"label.action.delete.node": "Delete node",
|
||||
"label.action.delete.oauth.provider": "Delete OAuth provider",
|
||||
"label.action.delete.physical.network": "Delete physical network",
|
||||
"label.action.delete.pod": "Delete Pod",
|
||||
"label.action.delete.primary.storage": "Delete primary storage",
|
||||
@ -450,6 +451,7 @@
|
||||
"label.clear": "Clear",
|
||||
"label.clear.list": "Clear list",
|
||||
"label.clear.notification": "Clear notification",
|
||||
"label.clientid": "Provider Client ID",
|
||||
"label.close": "Close",
|
||||
"label.cloud.managed": "CloudManaged",
|
||||
"label.cloudian.storage": "Cloudian storage",
|
||||
@ -797,6 +799,7 @@
|
||||
"label.enable.autoscale.vmgroup": "Enable AutoScale VM Group",
|
||||
"label.enable.host": "Enable Host",
|
||||
"label.enable.network.offering": "Enable network offering",
|
||||
"label.enable.oauth": "Enable OAuth Login",
|
||||
"label.enable.provider": "Enable provider",
|
||||
"label.enable.storage": "Enable storage pool",
|
||||
"label.enable.vpc.offering": "Enable VPC offering",
|
||||
@ -1410,6 +1413,8 @@
|
||||
"label.number": "#Rule",
|
||||
"label.numretries": "Number of retries",
|
||||
"label.nvpdeviceid": "ID",
|
||||
"label.oauth.configuration": "OAuth configuration",
|
||||
"label.oauth.verification": "OAuth verification",
|
||||
"label.ocfs2": "OCFS2",
|
||||
"label.of": "of",
|
||||
"label.of.month": "of month",
|
||||
@ -1629,11 +1634,13 @@
|
||||
"label.receivedbytes": "Bytes received",
|
||||
"label.recover.vm": "Recover VM",
|
||||
"label.redirect": "Redirect to:",
|
||||
"label.redirecturi": "Redirect URI",
|
||||
"label.redundantrouter": "Redundant router",
|
||||
"label.redundantstate": "Redundant state",
|
||||
"label.redundantvpcrouter": "Redundant VPC",
|
||||
"label.refresh": "Refresh",
|
||||
"label.region": "Region",
|
||||
"label.register.oauth": "Register OAuth",
|
||||
"label.register.template": "Register template",
|
||||
"label.register.user.data": "Register a userdata",
|
||||
"label.reinstall.vm": "Reinstall VM",
|
||||
@ -2344,6 +2351,7 @@
|
||||
"message.action.delete.network.static.route": "Please confirm that you want to remove this network Static Route",
|
||||
"message.action.delete.nexusvswitch": "Please confirm that you want to delete this nexus 1000v",
|
||||
"message.action.delete.node": "Please confirm that you want to delete this node.",
|
||||
"message.action.delete.oauth.provider": "Please confirm that you want to delete the OAuth provider.",
|
||||
"message.action.delete.physical.network": "Please confirm that you want to delete this physical network.",
|
||||
"message.action.delete.pod": "Please confirm that you want to delete this pod.",
|
||||
"message.action.delete.secondary.storage": "Please confirm that you want to delete this secondary storage.",
|
||||
|
||||
@ -70,3 +70,28 @@ export function logout () {
|
||||
notification.destroy()
|
||||
return api('logout')
|
||||
}
|
||||
|
||||
export function oauthlogin (arg) {
|
||||
if (!sourceToken.checkExistSource()) {
|
||||
sourceToken.init()
|
||||
}
|
||||
|
||||
// Logout before login is called to purge any duplicate sessionkey cookies
|
||||
api('logout')
|
||||
|
||||
const params = new URLSearchParams()
|
||||
params.append('command', 'oauthlogin')
|
||||
params.append('email', arg.email)
|
||||
params.append('secretcode', arg.secretcode)
|
||||
params.append('provider', arg.provider)
|
||||
params.append('domain', arg.domain)
|
||||
params.append('response', 'json')
|
||||
return axios({
|
||||
url: '/',
|
||||
method: 'post',
|
||||
data: params,
|
||||
headers: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
customDisplayItems () {
|
||||
return ['ip6routes', 'privatemtu', 'publicmtu']
|
||||
return ['ip6routes', 'privatemtu', 'publicmtu', 'provider']
|
||||
},
|
||||
vnfAccessMethods () {
|
||||
if (this.resource.templatetype === 'VNF' && ['vm', 'vnfapp'].includes(this.$route.meta.name)) {
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<template v-if="['name', 'provider'].includes(column.key) ">
|
||||
<span v-if="['vm', 'vnfapp'].includes($route.path.split('/')[1])" style="margin-right: 5px">
|
||||
<span v-if="record.icon && record.icon.base64image">
|
||||
<resource-icon :image="record.icon.base64image" size="2x"/>
|
||||
@ -50,7 +50,7 @@
|
||||
style="margin-left: 5px"
|
||||
:actions="actions"
|
||||
:resource="record"
|
||||
:enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'name' "
|
||||
:enabled="quickViewEnabled() && actions.length > 0 && columns && ['name', 'provider'].includes(columns[0].dataIndex)"
|
||||
@exec-action="$parent.execAction"/>
|
||||
<span v-if="$route.path.startsWith('/project')" style="margin-right: 5px">
|
||||
<tooltip-button type="dashed" size="small" icon="LoginOutlined" @onClick="changeProject(record)" />
|
||||
@ -586,7 +586,7 @@ export default {
|
||||
'/project', '/account',
|
||||
'/zone', '/pod', '/cluster', '/host', '/storagepool', '/imagestore', '/systemvm', '/router', '/ilbvm', '/annotation',
|
||||
'/computeoffering', '/systemoffering', '/diskoffering', '/backupoffering', '/networkoffering', '/vpcoffering',
|
||||
'/tungstenfabric', '/guestos', '/guestoshypervisormapping'].join('|'))
|
||||
'/tungstenfabric', '/oauthsetting', '/guestos', '/guestoshypervisormapping'].join('|'))
|
||||
.test(this.$route.path)
|
||||
},
|
||||
enableGroupAction () {
|
||||
|
||||
@ -301,6 +301,15 @@ export const constantRouterMap = [
|
||||
},
|
||||
component: () => import('@/views/dashboard/VerifyTwoFa')
|
||||
},
|
||||
{
|
||||
path: '/verifyOauth',
|
||||
name: 'VerifyOauth',
|
||||
meta: {
|
||||
title: 'label.oauth.verification',
|
||||
hidden: true
|
||||
},
|
||||
component: () => import('@/views/dashboard/VerifyOauth')
|
||||
},
|
||||
{
|
||||
path: '/setup2FA',
|
||||
name: 'SetupTwoFaAtLogin',
|
||||
|
||||
@ -70,6 +70,48 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'oauthsetting',
|
||||
title: 'label.oauth.configuration',
|
||||
icon: 'login-outlined',
|
||||
docHelp: 'adminguide/accounts.html#using-an-ldap-server-for-user-authentication',
|
||||
permission: ['listOauthProvider'],
|
||||
columns: ['provider', 'enabled', 'description', 'clientid', 'secretkey', 'redirecturi'],
|
||||
details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi'],
|
||||
actions: [
|
||||
{
|
||||
api: 'registerOauthProvider',
|
||||
icon: 'plus-outlined',
|
||||
label: 'label.register.oauth',
|
||||
listView: true,
|
||||
dataView: false,
|
||||
args: [
|
||||
'provider', 'description', 'clientid', 'redirecturi', 'secretkey'
|
||||
],
|
||||
mapping: {
|
||||
provider: {
|
||||
options: ['google', 'github']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'updateOauthProvider',
|
||||
icon: 'edit-outlined',
|
||||
label: 'label.edit',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['description', 'clientid', 'redirecturi', 'secretkey', 'enabled']
|
||||
},
|
||||
{
|
||||
api: 'deleteOauthProvider',
|
||||
icon: 'delete-outlined',
|
||||
label: 'label.action.delete.oauth.provider',
|
||||
message: 'message.action.delete.guest.os',
|
||||
dataView: true,
|
||||
popup: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'hypervisorcapability',
|
||||
title: 'label.hypervisor.capabilities',
|
||||
|
||||
@ -30,7 +30,7 @@ import { ACCESS_TOKEN, APIS, SERVER_MANAGER, CURRENT_PROJECT } from '@/store/mut
|
||||
|
||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
|
||||
const allowList = ['login'] // no redirect allowlist
|
||||
const allowList = ['login', 'VerifyOauth'] // no redirect allowlist
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
// start progress bar
|
||||
@ -56,6 +56,14 @@ router.beforeEach((to, from, next) => {
|
||||
|
||||
const validLogin = vueProps.$localStorage.get(ACCESS_TOKEN) || Cookies.get('userid') || Cookies.get('userid', { path: '/client' })
|
||||
if (validLogin) {
|
||||
var currentURL = new URL(window.location.href)
|
||||
var urlParams = new URLSearchParams(currentURL.search)
|
||||
var code = urlParams.get('code')
|
||||
if (code != null) {
|
||||
urlParams.delete('code')
|
||||
}
|
||||
currentURL.search = ''
|
||||
window.history.replaceState(null, null, currentURL.toString())
|
||||
if (to.path === '/user/login') {
|
||||
next({ path: '/dashboard' })
|
||||
NProgress.done()
|
||||
@ -134,7 +142,16 @@ router.beforeEach((to, from, next) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (allowList.includes(to.name)) {
|
||||
if (window.location.href.includes('verifyOauth') && to.name === undefined) {
|
||||
currentURL = new URL(window.location.href)
|
||||
urlParams = new URLSearchParams(currentURL.search)
|
||||
code = urlParams.get('code')
|
||||
urlParams.delete('verifyOauth')
|
||||
urlParams.delete('state')
|
||||
currentURL.search = '?code=' + code
|
||||
window.history.replaceState(null, null, currentURL.toString())
|
||||
next({ path: '/verifyOauth', query: { redirect: to.fullPath } })
|
||||
} else if (allowList.includes(to.name)) {
|
||||
next()
|
||||
} else {
|
||||
next({ path: '/user/login', query: { redirect: to.fullPath } })
|
||||
|
||||
@ -22,7 +22,7 @@ import notification from 'ant-design-vue/es/notification'
|
||||
import { vueProps } from '@/vue-app'
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
import { login, logout, api } from '@/api'
|
||||
import { oauthlogin, login, logout, api } from '@/api'
|
||||
import { i18n } from '@/locales'
|
||||
|
||||
import {
|
||||
@ -36,7 +36,9 @@ import {
|
||||
HEADER_NOTICES,
|
||||
DOMAIN_STORE,
|
||||
DARK_MODE,
|
||||
CUSTOM_COLUMNS
|
||||
CUSTOM_COLUMNS,
|
||||
OAUTH_DOMAIN,
|
||||
OAUTH_PROVIDER
|
||||
} from '@/store/mutation-types'
|
||||
|
||||
const user = {
|
||||
@ -159,6 +161,12 @@ const user = {
|
||||
},
|
||||
SET_READY_FOR_SHUTDOWN_POLLING_JOB: (state, job) => {
|
||||
state.readyForShutdownPollingJob = job
|
||||
},
|
||||
SET_DOMAIN_USED_TO_LOGIN: (state, domain) => {
|
||||
vueProps.$localStorage.set(OAUTH_DOMAIN, domain)
|
||||
},
|
||||
SET_OAUTH_PROVIDER_USED_TO_LOGIN: (state, provider) => {
|
||||
vueProps.$localStorage.set(OAUTH_PROVIDER, provider)
|
||||
}
|
||||
},
|
||||
|
||||
@ -213,6 +221,53 @@ const user = {
|
||||
})
|
||||
},
|
||||
|
||||
OauthLogin ({ commit }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
oauthlogin(userInfo).then(response => {
|
||||
const result = response.loginresponse || {}
|
||||
Cookies.set('account', result.account, { expires: 1 })
|
||||
Cookies.set('domainid', result.domainid, { expires: 1 })
|
||||
Cookies.set('role', result.type, { expires: 1 })
|
||||
Cookies.set('timezone', result.timezone, { expires: 1 })
|
||||
Cookies.set('timezoneoffset', result.timezoneoffset, { expires: 1 })
|
||||
Cookies.set('userfullname', result.firstname + ' ' + result.lastname, { expires: 1 })
|
||||
Cookies.set('userid', result.userid, { expires: 1 })
|
||||
Cookies.set('username', result.username, { expires: 1 })
|
||||
vueProps.$localStorage.set(ACCESS_TOKEN, result.sessionkey, 24 * 60 * 60 * 1000)
|
||||
commit('SET_TOKEN', result.sessionkey)
|
||||
commit('SET_TIMEZONE_OFFSET', result.timezoneoffset)
|
||||
|
||||
const cachedUseBrowserTimezone = vueProps.$localStorage.get(USE_BROWSER_TIMEZONE, false)
|
||||
commit('SET_USE_BROWSER_TIMEZONE', cachedUseBrowserTimezone)
|
||||
const darkMode = vueProps.$localStorage.get(DARK_MODE, false)
|
||||
commit('SET_DARK_MODE', darkMode)
|
||||
const cachedCustomColumns = vueProps.$localStorage.get(CUSTOM_COLUMNS, {})
|
||||
commit('SET_CUSTOM_COLUMNS', cachedCustomColumns)
|
||||
|
||||
commit('SET_APIS', {})
|
||||
commit('SET_NAME', '')
|
||||
commit('SET_AVATAR', '')
|
||||
commit('SET_INFO', {})
|
||||
commit('SET_PROJECT', {})
|
||||
commit('SET_HEADER_NOTICES', [])
|
||||
commit('SET_FEATURES', {})
|
||||
commit('SET_LDAP', {})
|
||||
commit('SET_CLOUDIAN', {})
|
||||
commit('SET_DOMAIN_STORE', {})
|
||||
commit('SET_LOGOUT_FLAG', false)
|
||||
commit('SET_2FA_ENABLED', (result.is2faenabled === 'true'))
|
||||
commit('SET_2FA_PROVIDER', result.providerfor2fa)
|
||||
commit('SET_2FA_ISSUER', result.issuerfor2fa)
|
||||
commit('SET_LOGIN_FLAG', false)
|
||||
notification.destroy()
|
||||
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
GetInfo ({ commit }, switchDomain) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cachedApis = switchDomain ? {} : vueProps.$localStorage.get(APIS, {})
|
||||
|
||||
@ -38,6 +38,8 @@ export const DARK_MODE = 'DARK_MODE'
|
||||
export const VUE_VERSION = 'VUE_VERSION'
|
||||
export const CUSTOM_COLUMNS = 'CUSTOM_COLUMNS'
|
||||
export const RELOAD_ALL_PROJECTS = 'RELOAD_ALL_PROJECTS'
|
||||
export const OAUTH_DOMAIN = 'OAUTH_DOMAIN'
|
||||
export const OAUTH_PROVIDER = 'OAUTH_PROVIDER'
|
||||
|
||||
export const CONTENT_WIDTH_TYPE = {
|
||||
Fluid: 'Fluid',
|
||||
|
||||
@ -681,7 +681,7 @@ export default {
|
||||
return this.$route.query.filter
|
||||
}
|
||||
const routeName = this.$route.name
|
||||
if ((this.projectView && routeName === 'vm') || (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) && ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool'].includes(routeName)) || ['account', 'guestnetwork', 'guestvlans', 'guestos', 'guestoshypervisormapping', 'kubernetes'].includes(routeName)) {
|
||||
if ((this.projectView && routeName === 'vm') || (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) && ['vm', 'iso', 'template', 'pod', 'cluster', 'host', 'systemvm', 'router', 'storagepool'].includes(routeName)) || ['account', 'guestnetwork', 'guestvlans', 'oauthsetting', 'guestos', 'guestoshypervisormapping', 'kubernetes'].includes(routeName)) {
|
||||
return 'all'
|
||||
}
|
||||
if (['publicip'].includes(routeName)) {
|
||||
@ -1779,6 +1779,8 @@ export default {
|
||||
query.hypervisor = value
|
||||
} else if (this.$route.name === 'guestos') {
|
||||
query.description = value
|
||||
} else if (this.$route.name === 'oauthsetting') {
|
||||
query.provider = value
|
||||
} else {
|
||||
query.keyword = value
|
||||
}
|
||||
|
||||
@ -153,6 +153,35 @@
|
||||
>{{ $t('label.login') }}</a-button>
|
||||
</a-form-item>
|
||||
<translation-menu/>
|
||||
<div class="content" v-if="socialLogin">
|
||||
<p class="or">or</p>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="social-auth" v-if="githubprovider">
|
||||
<a-button
|
||||
@click="handleGithubProviderAndDomain"
|
||||
tag="a"
|
||||
color="primary"
|
||||
:href="getGitHubUrl(from)"
|
||||
class="auth-btn github-auth"
|
||||
style="height: 38px; width: 185px; padding: 0; margin-bottom: 5px;" >
|
||||
<img src="/assets/github.svg" style="width: 32px; padding: 5px" />
|
||||
<a-text>Sign in with Github</a-text>
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="social-auth" v-if="googleprovider">
|
||||
<a-button
|
||||
@click="handleGoogleProviderAndDomain"
|
||||
tag="a"
|
||||
color="primary"
|
||||
:href="getGoogleUrl(from)"
|
||||
class="auth-btn google-auth"
|
||||
style="height: 38px; width: 185px; padding: 0" >
|
||||
<img src="/assets/google.svg" style="width: 32px; padding: 5px" />
|
||||
<a-text>Sign in with Google</a-text>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
@ -173,7 +202,18 @@ export default {
|
||||
return {
|
||||
idps: [],
|
||||
customActiveKey: 'cs',
|
||||
customActiveKeyOauth: false,
|
||||
loginBtn: false,
|
||||
email: '',
|
||||
secretcode: '',
|
||||
oauthexclude: '',
|
||||
socialLogin: false,
|
||||
googleprovider: false,
|
||||
githubprovider: false,
|
||||
googleredirecturi: '',
|
||||
githubredirecturi: '',
|
||||
googleclientid: '',
|
||||
githubclientid: '',
|
||||
loginType: 0,
|
||||
state: {
|
||||
time: 60,
|
||||
@ -199,7 +239,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['Login', 'Logout']),
|
||||
...mapActions(['Login', 'Logout', 'OauthLogin']),
|
||||
initForm () {
|
||||
this.formRef = ref()
|
||||
this.form = reactive({
|
||||
@ -209,7 +249,7 @@ export default {
|
||||
this.setRules()
|
||||
},
|
||||
setRules () {
|
||||
if (this.customActiveKey === 'cs') {
|
||||
if (this.customActiveKey === 'cs' && this.customActiveKeyOauth === false) {
|
||||
this.rules.username = [
|
||||
{
|
||||
required: true,
|
||||
@ -245,6 +285,24 @@ export default {
|
||||
this.form.idp = this.idps[0].id || ''
|
||||
}
|
||||
})
|
||||
api('listOauthProvider', {}).then(response => {
|
||||
if (response) {
|
||||
const oauthproviders = response.listoauthproviderresponse.oauthprovider || []
|
||||
oauthproviders.forEach(item => {
|
||||
this.socialLogin = true
|
||||
if (item.provider === 'google') {
|
||||
this.googleprovider = item.enabled
|
||||
this.googleclientid = item.clientid
|
||||
this.googleredirecturi = item.redirecturi
|
||||
}
|
||||
if (item.provider === 'github') {
|
||||
this.githubprovider = item.enabled
|
||||
this.githubclientid = item.clientid
|
||||
this.githubredirecturi = item.redirecturi
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// handler
|
||||
async handleUsernameOrEmail (rule, value) {
|
||||
@ -261,6 +319,53 @@ export default {
|
||||
this.customActiveKey = key
|
||||
this.setRules()
|
||||
},
|
||||
handleGithubProviderAndDomain () {
|
||||
this.handleDomain()
|
||||
this.$store.commit('SET_OAUTH_PROVIDER_USED_TO_LOGIN', 'github')
|
||||
},
|
||||
handleGoogleProviderAndDomain () {
|
||||
this.handleDomain()
|
||||
this.$store.commit('SET_OAUTH_PROVIDER_USED_TO_LOGIN', 'google')
|
||||
},
|
||||
handleDomain () {
|
||||
const values = toRaw(this.form)
|
||||
if (!values.domain) {
|
||||
this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', '/')
|
||||
} else {
|
||||
this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', values.domain)
|
||||
}
|
||||
},
|
||||
getGitHubUrl (from) {
|
||||
const rootURl = 'https://github.com/login/oauth/authorize'
|
||||
const options = {
|
||||
client_id: this.githubclientid,
|
||||
scope: 'user:email',
|
||||
state: 'cloudstack'
|
||||
}
|
||||
|
||||
const qs = new URLSearchParams(options)
|
||||
|
||||
return `${rootURl}?${qs.toString()}`
|
||||
},
|
||||
getGoogleUrl (from) {
|
||||
const rootUrl = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
const options = {
|
||||
redirect_uri: this.googleredirecturi,
|
||||
client_id: this.googleclientid,
|
||||
access_type: 'offline',
|
||||
response_type: 'code',
|
||||
prompt: 'consent',
|
||||
scope: [
|
||||
'https://www.googleapis.com/auth/userinfo.profile',
|
||||
'https://www.googleapis.com/auth/userinfo.email'
|
||||
].join(' '),
|
||||
state: from
|
||||
}
|
||||
|
||||
const qs = new URLSearchParams(options)
|
||||
|
||||
return `${rootUrl}?${qs.toString()}`
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (this.state.loginBtn) return
|
||||
@ -299,6 +404,28 @@ export default {
|
||||
this.formRef.value.scrollToField(error.errorFields[0].name)
|
||||
})
|
||||
},
|
||||
handleSubmitOauth (provider) {
|
||||
this.customActiveKeyOauth = true
|
||||
this.setRules()
|
||||
this.formRef.value.validate().then(() => {
|
||||
const values = toRaw(this.form)
|
||||
const loginParams = { ...values }
|
||||
delete loginParams.username
|
||||
loginParams.email = this.email
|
||||
loginParams.provider = provider
|
||||
loginParams.secretcode = this.secretcode
|
||||
loginParams.domain = values.domain
|
||||
if (!loginParams.domain) {
|
||||
loginParams.domain = '/'
|
||||
}
|
||||
this.OauthLogin(loginParams)
|
||||
.then((res) => this.loginSuccess(res))
|
||||
.catch(err => {
|
||||
this.requestFailed(err)
|
||||
this.state.loginBtn = false
|
||||
})
|
||||
})
|
||||
},
|
||||
loginSuccess (res) {
|
||||
this.$notification.destroy()
|
||||
this.$store.commit('SET_COUNT_NOTIFY', 0)
|
||||
@ -315,6 +442,9 @@ export default {
|
||||
if (err && err.response && err.response.data && err.response.data.loginresponse) {
|
||||
const error = err.response.data.loginresponse.errorcode + ': ' + err.response.data.loginresponse.errortext
|
||||
this.$message.error(`${this.$t('label.error')} ${error}`)
|
||||
} else if (err && err.response && err.response.data && err.response.data.oauthloginresponse) {
|
||||
const error = err.response.data.oauthloginresponse.errorcode + ': ' + err.response.data.oauthloginresponse.errortext
|
||||
this.$message.error(`${this.$t('label.error')} ${error}`)
|
||||
} else {
|
||||
this.$message.error(this.$t('message.login.failed'))
|
||||
}
|
||||
@ -372,6 +502,34 @@ export default {
|
||||
.register {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.g-btn-wrapper {
|
||||
background-color: rgb(221, 75, 57);
|
||||
height: 40px;
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
.center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 10px auto;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.or {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
background:
|
||||
linear-gradient(#CCC 0 0) left,
|
||||
linear-gradient(#CCC 0 0) right;
|
||||
background-size: 40% 1px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
93
ui/src/views/dashboard/VerifyOauth.vue
Normal file
93
ui/src/views/dashboard/VerifyOauth.vue
Normal file
@ -0,0 +1,93 @@
|
||||
// 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.
|
||||
|
||||
<template>
|
||||
<div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '@/store'
|
||||
import { mapActions } from 'vuex'
|
||||
import { api } from '@/api'
|
||||
import { OAUTH_DOMAIN, OAUTH_PROVIDER } from '@/store/mutation-types'
|
||||
|
||||
export default {
|
||||
name: 'VerifyOauth',
|
||||
data () {
|
||||
return {
|
||||
state: {
|
||||
time: 60,
|
||||
loginBtn: false,
|
||||
loginType: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.verifyOauth()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['Login', 'Logout', 'OauthLogin']),
|
||||
verifyOauth () {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const code = params.get('code')
|
||||
const provider = this.$localStorage.get(OAUTH_PROVIDER)
|
||||
this.state.loginBtn = true
|
||||
api('verifyOAuthCodeAndGetUser', { provider: provider, secretcode: code }).then(response => {
|
||||
const email = response.verifyoauthcodeandgetuserresponse.oauthemail.email
|
||||
const loginParams = {}
|
||||
loginParams.email = email
|
||||
loginParams.provider = provider
|
||||
loginParams.secretcode = code
|
||||
loginParams.domain = this.$localStorage.get(OAUTH_DOMAIN)
|
||||
this.OauthLogin(loginParams)
|
||||
.then((res) => this.loginSuccess(res))
|
||||
.catch(err => {
|
||||
this.requestFailed(err)
|
||||
this.state.loginBtn = false
|
||||
})
|
||||
}).catch(err => {
|
||||
this.requestFailed(err)
|
||||
this.state.loginBtn = false
|
||||
})
|
||||
},
|
||||
loginSuccess (res) {
|
||||
this.$notification.destroy()
|
||||
this.$store.commit('SET_COUNT_NOTIFY', 0)
|
||||
if (store.getters.twoFaEnabled === true && store.getters.twoFaProvider !== '' && store.getters.twoFaProvider !== undefined) {
|
||||
this.$router.push({ path: '/verify2FA' }).catch(() => {})
|
||||
} else if (store.getters.twoFaEnabled === true && (store.getters.twoFaProvider === '' || store.getters.twoFaProvider === undefined)) {
|
||||
this.$router.push({ path: '/setup2FA' }).catch(() => {})
|
||||
} else {
|
||||
this.$store.commit('SET_LOGIN_FLAG', true)
|
||||
this.$router.push({ path: '/dashboard' }).catch(() => {})
|
||||
}
|
||||
},
|
||||
requestFailed (err) {
|
||||
this.$store.dispatch('Logout').then(() => {
|
||||
this.$router.replace({ path: '/user/login' })
|
||||
})
|
||||
if (err && err.response && err.response.data && err.response.data.oauthloginresponse) {
|
||||
const error = err.response.data.oauthloginresponse.errorcode + ': ' + err.response.data.oauthloginresponse.errortext
|
||||
this.$message.error(`${this.$t('label.error')} ${error}`)
|
||||
} else {
|
||||
this.$message.error(this.$t('message.login.failed'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user