Merge branch 'main' of https://github.com/apache/cloudstack into nsx-integration

This commit is contained in:
Pearl Dsilva 2023-10-31 06:10:52 -04:00
commit 20cb9f56f3
59 changed files with 3427 additions and 31 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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).

View File

@ -95,5 +95,4 @@ public class CreateSSHKeyPairCmd extends BaseCmd {
response.setObjectName("keypair");
setResponseObject(response);
}
}
}

View File

@ -76,5 +76,4 @@ public class ListUserDataCmd extends BaseListProjectAndAccountResourcesCmd {
response.setResponseName(getCommandName());
setResponseObject(response);
}
}
}

View File

@ -142,5 +142,4 @@ public class RegisterUserDataCmd extends BaseCmd {
response.setObjectName(ApiConstants.USER_DATA);
setResponseObject(response);
}
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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"

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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>

View 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>

View File

@ -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);
}

View File

@ -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};
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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) {
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name=oauth2
parent=api

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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

View File

@ -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',

View File

@ -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"
},

View 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

View 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

View File

@ -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.",

View File

@ -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'
}
})
}

View File

@ -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)) {

View File

@ -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 () {

View File

@ -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',

View File

@ -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',

View File

@ -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 } })

View File

@ -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, {})

View File

@ -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',

View File

@ -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
}

View File

@ -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>

View 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>