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