mirror of https://github.com/apache/cloudstack.git
Merge 5696366cdd into b7d4df0a11
This commit is contained in:
commit
539b13cd4e
|
|
@ -42,8 +42,19 @@ public interface UserOAuth2Authenticator extends Adapter {
|
|||
* Verifies the code provided by provider and fetches email
|
||||
* @return returns email
|
||||
*/
|
||||
String verifyCodeAndFetchEmail(String secretCode);
|
||||
String verifySecretCodeAndFetchEmail(String secretCode);
|
||||
|
||||
/**
|
||||
* Verifies if the logged in user is valid for a specific domain
|
||||
* @return true if it's a valid user, otherwise false
|
||||
*/
|
||||
boolean verifyUser(String email, String secretCode, Long domainId);
|
||||
|
||||
/**
|
||||
* Verifies the secret code provided by provider and fetches email for a specific domain
|
||||
* @return email for the specified domain
|
||||
*/
|
||||
String verifySecretCodeAndFetchEmail(String secretCode, Long domainId);
|
||||
|
||||
/**
|
||||
* Fetches email using the accessToken
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@
|
|||
-- Schema upgrade from 4.22.1.0 to 4.23.0.0
|
||||
--;
|
||||
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider', 'domain_id', 'bigint unsigned DEFAULT NULL COMMENT "NULL for global provider, domain ID for domain-specific" AFTER `redirect_uri`');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.oauth_provider', 'fk_oauth_provider__domain_id', '(`domain_id`)', '`domain`(`id`)');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_KEY`('i_oauth_provider__domain_id', 'cloud.oauth_provider', '(`domain_id`)');
|
||||
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_UNIQUE_KEY`('cloud.oauth_provider', 'uk_oauth_provider__provider_domain', '(`provider`, `domain_id`)');
|
||||
|
||||
CREATE TABLE `cloud`.`backup_offering_details` (
|
||||
`id` bigint unsigned NOT NULL auto_increment,
|
||||
`backup_offering_id` bigint unsigned NOT NULL COMMENT 'Backup offering id',
|
||||
|
|
|
|||
|
|
@ -269,6 +269,17 @@ public class ConfigKey<T> {
|
|||
|
||||
private String _defaultValueIfEmpty = null;
|
||||
|
||||
private boolean _strictScope = false;
|
||||
|
||||
public boolean isStrictScope() {
|
||||
return _strictScope;
|
||||
}
|
||||
|
||||
public ConfigKey<T> withStrictScope() {
|
||||
this._strictScope = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static void init(ConfigDepotImpl depot) {
|
||||
s_depot = depot;
|
||||
}
|
||||
|
|
@ -429,11 +440,18 @@ public class ConfigKey<T> {
|
|||
}
|
||||
|
||||
public T valueInScope(Scope scope, Long id) {
|
||||
return valueInScope(scope, id, false);
|
||||
}
|
||||
|
||||
public T valueInScope(Scope scope, Long id, boolean strictScope) {
|
||||
if (id == null) {
|
||||
return value();
|
||||
return strictScope ? null : value();
|
||||
}
|
||||
String value = s_depot != null ? s_depot.getConfigStringValue(_name, scope, id) : null;
|
||||
if (value == null) {
|
||||
if (strictScope) {
|
||||
return null;
|
||||
}
|
||||
return valueInGlobalOrAvailableParentScope(scope, id);
|
||||
}
|
||||
logger.trace("Scope({}) value for config ({}): {}", scope, _name, _value);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
//
|
||||
package org.apache.cloudstack.oauth2;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.utils.component.PluggableService;
|
||||
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
|
|
@ -27,10 +28,15 @@ import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
|
|||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface OAuth2AuthManager extends PluggableAPIAuthenticator, PluggableService {
|
||||
String GLOBAL_DOMAIN_FILTER = "-1";
|
||||
Long GLOBAL_DOMAIN_ID = -1L;
|
||||
|
||||
public static ConfigKey<Boolean> OAuth2IsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "oauth2.enabled", "false",
|
||||
"Indicates whether OAuth plugin is enabled or not", false);
|
||||
"Indicates whether OAuth plugin is enabled or not. This can be configured at domain level.", true, ConfigKey.Scope.Domain)
|
||||
.withStrictScope();
|
||||
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", "",
|
||||
|
|
@ -49,13 +55,30 @@ public interface OAuth2AuthManager extends PluggableAPIAuthenticator, PluggableS
|
|||
*/
|
||||
UserOAuth2Authenticator getUserOAuth2AuthenticationProvider(final String providerName);
|
||||
|
||||
String verifyCodeAndFetchEmail(String code, String provider);
|
||||
String verifySecretCodeAndFetchEmail(String code, String provider, Long domainId);
|
||||
|
||||
OauthProviderVO registerOauthProvider(RegisterOAuthProviderCmd cmd);
|
||||
|
||||
List<OauthProviderVO> listOauthProviders(String provider, String uuid);
|
||||
List<OauthProviderVO> listOauthProviders(String provider, String uuid, Long domainId);
|
||||
|
||||
boolean deleteOauthProvider(Long id);
|
||||
|
||||
OauthProviderVO updateOauthProvider(UpdateOAuthProviderCmd cmd);
|
||||
|
||||
Long resolveDomainId(Map<String, Object[]> params);
|
||||
|
||||
/**
|
||||
* Resolves whether the OAuth plugin is enabled for the given domain scope.
|
||||
* A null domain or the ROOT domain is treated as the global scope, since the
|
||||
* ROOT domain has no domain-level override and inherits the global value;
|
||||
* any other domain is checked strictly at its own domain scope (no inheritance).
|
||||
* @param domainId domain id, or null for global
|
||||
* @return true if OAuth is enabled for that scope
|
||||
*/
|
||||
static boolean isPluginEnabledForDomain(Long domainId) {
|
||||
if (domainId == null || domainId == Domain.ROOT_DOMAIN) {
|
||||
return Boolean.TRUE.equals(OAuth2IsPluginEnabled.value());
|
||||
}
|
||||
return Boolean.TRUE.equals(OAuth2IsPluginEnabled.valueInScope(ConfigKey.Scope.Domain, domainId, true));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
|
@ -39,15 +41,28 @@ import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
|||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.domain.DomainVO;
|
||||
import com.cloud.user.DomainManager;
|
||||
import com.cloud.user.DomainService;
|
||||
import com.cloud.utils.component.Manager;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.framework.messagebus.MessageBus;
|
||||
|
||||
public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthManager, Manager, Configurable {
|
||||
|
||||
@Inject
|
||||
protected OauthProviderDao _oauthProviderDao;
|
||||
|
||||
@Inject
|
||||
private DomainService _domainService;
|
||||
|
||||
@Inject
|
||||
private MessageBus _messageBus;
|
||||
|
||||
protected static Map<String, UserOAuth2Authenticator> userOAuth2AuthenticationProvidersMap = new HashMap<>();
|
||||
|
||||
private List<UserOAuth2Authenticator> userOAuth2AuthenticationProviders;
|
||||
|
|
@ -63,17 +78,29 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
|
||||
@Override
|
||||
public boolean start() {
|
||||
if (isOAuthPluginEnabled()) {
|
||||
logger.info("OAUTH plugin loaded");
|
||||
initializeUserOAuth2AuthenticationProvidersMap();
|
||||
} else {
|
||||
logger.info("OAUTH plugin not enabled so not loading");
|
||||
}
|
||||
initializeUserOAuth2AuthenticationProvidersMap();
|
||||
addDomainRemovalListener();
|
||||
logger.info("OAUTH plugin loaded");
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isOAuthPluginEnabled() {
|
||||
return OAuth2IsPluginEnabled.value();
|
||||
private void addDomainRemovalListener() {
|
||||
_messageBus.subscribe(DomainManager.MESSAGE_PRE_REMOVE_DOMAIN_EVENT, (senderAddress, subject, args) -> {
|
||||
try {
|
||||
long domainId = ((DomainVO) args).getId();
|
||||
List<OauthProviderVO> providers = _oauthProviderDao.listByDomain(domainId);
|
||||
for (OauthProviderVO provider : providers) {
|
||||
_oauthProviderDao.expunge(provider.getId());
|
||||
logger.debug("Removed OAuth provider {} for deleted domain {}", provider.getProvider(), domainId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to remove OAuth providers for deleted domain", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean isOAuthPluginEnabled(Long domainId) {
|
||||
return OAuth2AuthManager.isPluginEnabledForDomain(domainId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -124,9 +151,11 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String code, String provider) {
|
||||
public String verifySecretCodeAndFetchEmail(String code, String provider, Long domainId) {
|
||||
UserOAuth2Authenticator authenticator = getUserOAuth2AuthenticationProvider(provider);
|
||||
return authenticator.verifyCodeAndFetchEmail(code);
|
||||
String email = authenticator.verifySecretCodeAndFetchEmail(code, domainId);
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -136,27 +165,38 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
String clientId = StringUtils.trim(cmd.getClientId());
|
||||
String redirectUri = StringUtils.trim(cmd.getRedirectUri());
|
||||
String secretKey = StringUtils.trim(cmd.getSecretKey());
|
||||
Long domainId = normalizeGlobalScope(resolveDomainIdFromIdOrPath(cmd.getDomainId(), cmd.getDomainPath()));
|
||||
String authorizeUrl = StringUtils.trim(cmd.getAuthorizeUrl());
|
||||
String tokenUrl = StringUtils.trim(cmd.getTokenUrl());
|
||||
|
||||
if (!isOAuthPluginEnabled()) {
|
||||
if (!isOAuthPluginEnabled(domainId)) {
|
||||
throw new CloudRuntimeException("OAuth is not enabled, please enable to register");
|
||||
}
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProvider(provider);
|
||||
|
||||
// Check for existing provider with same name and domain
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProviderAndDomain(provider, domainId);
|
||||
if (providerVO != null) {
|
||||
throw new CloudRuntimeException(String.format("Provider with the name %s is already registered", provider));
|
||||
if (domainId == null) {
|
||||
throw new CloudRuntimeException(String.format("Global provider with the name %s is already registered", provider));
|
||||
} else {
|
||||
throw new CloudRuntimeException(String.format("Provider with the name %s is already registered for domain %d", provider, domainId));
|
||||
}
|
||||
}
|
||||
|
||||
return saveOauthProvider(provider, description, clientId, secretKey, redirectUri, authorizeUrl, tokenUrl);
|
||||
return saveOauthProvider(provider, description, clientId, secretKey, redirectUri, authorizeUrl, tokenUrl, domainId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OauthProviderVO> listOauthProviders(String provider, String uuid) {
|
||||
public List<OauthProviderVO> listOauthProviders(String provider, String uuid, Long domainId) {
|
||||
List<OauthProviderVO> providers;
|
||||
if (uuid != null) {
|
||||
providers = Collections.singletonList(_oauthProviderDao.findByUuid(uuid));
|
||||
} else if (StringUtils.isNotBlank(provider) && domainId != null) {
|
||||
providers = Collections.singletonList(_oauthProviderDao.findByProviderAndDomain(provider, domainId));
|
||||
} else if (StringUtils.isNotBlank(provider)) {
|
||||
providers = Collections.singletonList(_oauthProviderDao.findByProvider(provider));
|
||||
providers = Collections.singletonList(_oauthProviderDao.findByProviderAndDomain(provider, null));
|
||||
} else if (domainId != null) {
|
||||
providers = _oauthProviderDao.listByDomainIncludingGlobal(domainId);
|
||||
} else {
|
||||
providers = _oauthProviderDao.listAll();
|
||||
}
|
||||
|
|
@ -179,6 +219,30 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
throw new CloudRuntimeException("Provider with the given id is not there");
|
||||
}
|
||||
|
||||
Long targetDomainId = providerVO.getDomainId();
|
||||
if (cmd.getDomainId() != null || StringUtils.isNotEmpty(cmd.getDomainPath())) {
|
||||
Long resolved = resolveDomainIdFromIdOrPath(cmd.getDomainId(), cmd.getDomainPath());
|
||||
if (resolved == null) {
|
||||
throw new CloudRuntimeException("Unable to resolve the supplied domain. Provide a valid domain id or path.");
|
||||
}
|
||||
resolved = normalizeGlobalScope(resolved);
|
||||
if (!Objects.equals(resolved, providerVO.getDomainId())) {
|
||||
OauthProviderVO existing = _oauthProviderDao.findByProviderAndDomain(providerVO.getProvider(), resolved);
|
||||
if (existing != null) {
|
||||
throw new CloudRuntimeException(String.format(
|
||||
"Provider with the name %s is already registered for domain %s", providerVO.getProvider(),
|
||||
resolved == null ? "ROOT (global)" : resolved));
|
||||
}
|
||||
}
|
||||
targetDomainId = resolved;
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(enabled) && !isOAuthPluginEnabled(targetDomainId)) {
|
||||
throw new CloudRuntimeException(String.format(
|
||||
"OAuth plugin is not enabled %s. Enable oauth2.enabled at that scope before enabling this provider.",
|
||||
targetDomainId == null ? "globally" : "for this domain"));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(description)) {
|
||||
providerVO.setDescription(description);
|
||||
}
|
||||
|
|
@ -200,13 +264,14 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
if (enabled != null) {
|
||||
providerVO.setEnabled(enabled);
|
||||
}
|
||||
providerVO.setDomainId(targetDomainId);
|
||||
|
||||
_oauthProviderDao.update(id, providerVO);
|
||||
|
||||
return _oauthProviderDao.findById(id);
|
||||
}
|
||||
|
||||
private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri, String authorizeUrl, String tokenUrl) {
|
||||
private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri, String authorizeUrl, String tokenUrl, Long domainId) {
|
||||
final OauthProviderVO oauthProviderVO = new OauthProviderVO();
|
||||
|
||||
oauthProviderVO.setProvider(provider);
|
||||
|
|
@ -214,6 +279,7 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
oauthProviderVO.setClientId(clientId);
|
||||
oauthProviderVO.setSecretKey(secretKey);
|
||||
oauthProviderVO.setRedirectUri(redirectUri);
|
||||
oauthProviderVO.setDomainId(domainId);
|
||||
oauthProviderVO.setAuthorizeUrl(authorizeUrl);
|
||||
oauthProviderVO.setTokenUrl(tokenUrl);
|
||||
oauthProviderVO.setEnabled(true);
|
||||
|
|
@ -225,7 +291,66 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
|
||||
@Override
|
||||
public boolean deleteOauthProvider(Long id) {
|
||||
return _oauthProviderDao.remove(id);
|
||||
return _oauthProviderDao.expunge(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long resolveDomainId(Map<String, Object[]> params) {
|
||||
final String[] domainIdArray = (String[])params.get(ApiConstants.DOMAIN_ID);
|
||||
if (ArrayUtils.isNotEmpty(domainIdArray)) {
|
||||
String domainUuid = domainIdArray[0];
|
||||
if (GLOBAL_DOMAIN_FILTER.equals(domainUuid)) {
|
||||
return GLOBAL_DOMAIN_ID;
|
||||
}
|
||||
Domain domain = _domainService.getDomain(domainUuid);
|
||||
if (Objects.nonNull(domain)) {
|
||||
return domain.getId();
|
||||
}
|
||||
}
|
||||
final String[] domainArray = (String[])params.get(ApiConstants.DOMAIN);
|
||||
if (ArrayUtils.isNotEmpty(domainArray)) {
|
||||
String path = normalizeDomainPath(domainArray[0]);
|
||||
if (StringUtils.isNotEmpty(path)) {
|
||||
Domain domain = _domainService.findDomainByIdOrPath(null, path);
|
||||
if (Objects.nonNull(domain)) {
|
||||
return domain.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Long resolveDomainIdFromIdOrPath(Long domainId, String domainPath) {
|
||||
if (domainId != null) {
|
||||
return domainId;
|
||||
}
|
||||
String path = normalizeDomainPath(domainPath);
|
||||
if (StringUtils.isNotEmpty(path)) {
|
||||
Domain domain = _domainService.findDomainByIdOrPath(null, path);
|
||||
if (Objects.nonNull(domain)) {
|
||||
return domain.getId();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// The ROOT domain is the top of the tree, so a provider scoped to it is equivalent
|
||||
// to a global provider; treat it as global so the global oauth2.enabled config applies.
|
||||
protected Long normalizeGlobalScope(Long domainId) {
|
||||
return (domainId != null && Domain.ROOT_DOMAIN == domainId) ? null : domainId;
|
||||
}
|
||||
|
||||
protected String normalizeDomainPath(String path) {
|
||||
if (StringUtils.isEmpty(path)) {
|
||||
return null;
|
||||
}
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (!path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@ import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
|||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.cloudstack.oauth2.OAuth2AuthManager.OAuth2IsPluginEnabled;
|
||||
import java.util.Objects;
|
||||
|
||||
public class OAuth2UserAuthenticator extends AdapterBase implements UserAuthenticator {
|
||||
|
||||
|
|
@ -49,7 +48,7 @@ public class OAuth2UserAuthenticator extends AdapterBase implements UserAuthenti
|
|||
logger.debug("Trying OAuth2 auth for user: " + username);
|
||||
}
|
||||
|
||||
if (!isOAuthPluginEnabled()) {
|
||||
if (!isOAuthPluginEnabled(domainId)) {
|
||||
logger.debug("OAuth2 plugin is disabled");
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
|
||||
} else if (requestParameters == null) {
|
||||
|
|
@ -76,7 +75,7 @@ public class OAuth2UserAuthenticator extends AdapterBase implements UserAuthenti
|
|||
String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
|
||||
|
||||
UserOAuth2Authenticator authenticator = userOAuth2mgr.getUserOAuth2AuthenticationProvider(oauthProvider);
|
||||
if (user != null && authenticator.verifyUser(email, secretCode)) {
|
||||
if (Objects.nonNull(user) && authenticator.verifyUser(email, secretCode, domainId)) {
|
||||
return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +88,7 @@ public class OAuth2UserAuthenticator extends AdapterBase implements UserAuthenti
|
|||
return null;
|
||||
}
|
||||
|
||||
protected boolean isOAuthPluginEnabled() {
|
||||
return OAuth2IsPluginEnabled.value();
|
||||
protected boolean isOAuthPluginEnabled(Long domainId) {
|
||||
return OAuth2AuthManager.isPluginEnabledForDomain(domainId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,14 +35,17 @@ 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.DomainResponse;
|
||||
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.commons.lang3.ArrayUtils;
|
||||
|
||||
import com.cloud.api.ApiDBUtils;
|
||||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.Account;
|
||||
|
||||
@APICommand(name = "listOauthProvider", description = "List OAuth providers registered", responseObject = OauthProviderResponse.class, entityType = {},
|
||||
|
|
@ -60,6 +63,14 @@ public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticat
|
|||
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the provider")
|
||||
private String provider;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
|
||||
description = "Domain ID to list OAuth providers for a specific domain. Use -1 for global providers only.", since = "4.23.0")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING,
|
||||
description = "Domain path for domain-specific OAuth provider lookup. Ignored when Domain ID is passed.", since = "4.23.0")
|
||||
private String domainPath;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -71,6 +82,10 @@ public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticat
|
|||
return provider;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -99,7 +114,26 @@ public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticat
|
|||
provider = providerArray[0];
|
||||
}
|
||||
|
||||
List<OauthProviderVO> resultList = _oauth2mgr.listOauthProviders(provider, id);
|
||||
boolean domainRequested = ArrayUtils.isNotEmpty((String[])params.get(ApiConstants.DOMAIN_ID))
|
||||
|| ArrayUtils.isNotEmpty((String[])params.get(ApiConstants.DOMAIN));
|
||||
domainId = _oauth2mgr.resolveDomainId(params);
|
||||
|
||||
if (domainRequested && domainId == null) {
|
||||
ListResponse<OauthProviderResponse> response = new ListResponse<>();
|
||||
response.setResponses(new ArrayList<>(), 0);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
return ApiResponseSerializer.toSerializedString(response, responseType);
|
||||
}
|
||||
|
||||
List<OauthProviderVO> resultList = _oauth2mgr.listOauthProviders(provider, id, domainId);
|
||||
boolean isAuthenticated = session != null && session.getAttribute(ApiConstants.USER_ID) != null;
|
||||
if (domainRequested && domainId != null && domainId > 0) {
|
||||
resultList.removeIf(p -> p.getDomainId() == null);
|
||||
} else if (!domainRequested && !isAuthenticated) {
|
||||
resultList.removeIf(p -> p.getDomainId() != null);
|
||||
}
|
||||
|
||||
List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauth2mgr.listUserOAuth2AuthenticationProviders();
|
||||
List<String> authenticatorPluginNames = new ArrayList<>();
|
||||
for (UserOAuth2Authenticator authenticator : userOAuth2AuthenticatorPlugins) {
|
||||
|
|
@ -108,9 +142,11 @@ public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticat
|
|||
}
|
||||
List<OauthProviderResponse> responses = new ArrayList<>();
|
||||
for (OauthProviderVO result : resultList) {
|
||||
Domain domain = result.getDomainId() != null ? ApiDBUtils.findDomainById(result.getDomainId()) : null;
|
||||
OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), result.getAuthorizeUrl(), result.getTokenUrl());
|
||||
if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), result.getAuthorizeUrl(), result.getTokenUrl(), domain);
|
||||
boolean oauthEnabled = OAuth2AuthManager.isPluginEnabledForDomain(result.getDomainId());
|
||||
if (oauthEnabled && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
|
||||
r.setEnabled(true);
|
||||
} else {
|
||||
r.setEnabled(false);
|
||||
|
|
@ -119,8 +155,20 @@ public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticat
|
|||
responses.add(r);
|
||||
}
|
||||
|
||||
int totalEnabledCount = responses.size();
|
||||
if (!domainRequested && !isAuthenticated) {
|
||||
List<OauthProviderVO> allProviders = _oauth2mgr.listOauthProviders(null, null, null);
|
||||
for (OauthProviderVO domainProvider : allProviders) {
|
||||
if (domainProvider.getDomainId() != null && domainProvider.isEnabled()
|
||||
&& OAuth2AuthManager.isPluginEnabledForDomain(domainProvider.getDomainId())
|
||||
&& authenticatorPluginNames.contains(domainProvider.getProvider())) {
|
||||
totalEnabledCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListResponse<OauthProviderResponse> response = new ListResponse<>();
|
||||
response.setResponses(responses, resultList.size());
|
||||
response.setResponses(responses, totalEnabledCount);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.oauth2.api.command;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.cloud.api.ApiServlet;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.User;
|
||||
|
|
@ -48,7 +50,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.net.InetAddress;
|
||||
|
||||
import static org.apache.cloudstack.oauth2.OAuth2AuthManager.OAuth2IsPluginEnabled;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
|
||||
@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.",
|
||||
|
|
@ -120,9 +122,6 @@ public class OauthLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||
|
||||
@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);
|
||||
|
|
@ -130,15 +129,41 @@ public class OauthLoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||
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");
|
||||
|
||||
try {
|
||||
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);
|
||||
|
||||
final Domain userDomain = _domainService.findDomainByIdOrPath(domainId, domain);
|
||||
if (Objects.nonNull(userDomain)) {
|
||||
domainId = userDomain.getId();
|
||||
}
|
||||
|
||||
boolean oauthEnabled = OAuth2AuthManager.isPluginEnabledForDomain(domainId);
|
||||
if (!oauthEnabled) {
|
||||
logger.debug(String.format("OAuth is not enabled %s, users cannot login using OAuth", domainId == null ? "globally" : "in domain " + domainId));
|
||||
throw new CloudAuthenticationException(String.format(
|
||||
"OAuth login is not enabled %s. Please contact your administrator.",
|
||||
domainId == null ? "globally" : "for this domain"));
|
||||
}
|
||||
|
||||
return doOauthAuthentication(session, domainId, domain, email, params, remoteAddress, responseType, auditTrailSb);
|
||||
} catch (final CloudAuthenticationException ex) {
|
||||
throw toServerApiException(session, params, responseType, auditTrailSb, ex);
|
||||
}
|
||||
}
|
||||
|
||||
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 ServerApiException toServerApiException(HttpSession session, Map<String, Object[]> params, String responseType, StringBuilder auditTrailSb, CloudAuthenticationException ex) {
|
||||
ApiServlet.invalidateHttpSession(session, "fall through to API key,");
|
||||
String msg = ex.getMessage() != null ? ex.getMessage() : "failed to authenticate user via OAuth";
|
||||
auditTrailSb.append(" " + ApiErrorCode.ACCOUNT_ERROR + " " + msg);
|
||||
String serializedResponse = _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(), msg, params, responseType);
|
||||
return new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, serializedResponse);
|
||||
}
|
||||
|
||||
private String doOauthAuthentication(HttpSession session, Long domainId, String domain, String email, Map<String, Object[]> params, InetAddress remoteAddress, String responseType, StringBuilder auditTrailSb) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ 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.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
|
|
@ -35,6 +36,8 @@ import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
|||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.cloud.api.ApiDBUtils;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
|
||||
@APICommand(name = "registerOauthProvider", responseObject = SuccessResponse.class, description = "Register the OAuth2 provider in CloudStack", since = "4.19.0")
|
||||
|
|
@ -59,6 +62,14 @@ public class RegisterOAuthProviderCmd extends BaseCmd {
|
|||
@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.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
|
||||
description = "Domain ID for domain-specific OAuth provider. If not provided, registers as global provider", since = "4.23.0")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING,
|
||||
description = "Domain path for domain-specific OAuth provider. Ignored when Domain ID is passed.", since = "4.23.0")
|
||||
private String domainPath;
|
||||
|
||||
@Parameter(name = ApiConstants.AUTHORIZE_URL, type = CommandType.STRING, description = "Authorize URL for OAuth initialization (only required for keycloak provider)")
|
||||
private String authorizeUrl;
|
||||
|
||||
|
|
@ -94,6 +105,14 @@ public class RegisterOAuthProviderCmd extends BaseCmd {
|
|||
return redirectUri;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public String getDomainPath() {
|
||||
return domainPath;
|
||||
}
|
||||
|
||||
public String getAuthorizeUrl() {
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
|
@ -126,9 +145,10 @@ public class RegisterOAuthProviderCmd extends BaseCmd {
|
|||
|
||||
OauthProviderVO provider = _oauth2mgr.registerOauthProvider(this);
|
||||
|
||||
Domain domain = provider.getDomainId() != null ? ApiDBUtils.findDomainById(provider.getDomainId()) : null;
|
||||
OauthProviderResponse response = new OauthProviderResponse(provider.getUuid(), provider.getProvider(),
|
||||
provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri(),
|
||||
provider.getAuthorizeUrl(), provider.getTokenUrl());
|
||||
provider.getAuthorizeUrl(), provider.getTokenUrl(), domain);
|
||||
response.setResponseName(getCommandName());
|
||||
response.setObjectName(ApiConstants.OAUTH_PROVIDER);
|
||||
setResponseObject(response);
|
||||
|
|
|
|||
|
|
@ -28,12 +28,16 @@ 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.DomainResponse;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
import com.cloud.api.ApiDBUtils;
|
||||
import com.cloud.domain.Domain;
|
||||
|
||||
@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 {
|
||||
|
|
@ -66,6 +70,14 @@ public final class UpdateOAuthProviderCmd extends BaseCmd {
|
|||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "OAuth provider will be enabled or disabled based on this value")
|
||||
private Boolean enabled;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
|
||||
description = "Domain ID to reassign this OAuth provider to. If not provided, the current domain assignment is kept.", since = "4.23.0")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING,
|
||||
description = "Domain path to reassign this OAuth provider to. Ignored when Domain ID is passed. If neither is provided, the current domain assignment is kept.", since = "4.23.0")
|
||||
private String domainPath;
|
||||
|
||||
@Inject
|
||||
OAuth2AuthManager _oauthMgr;
|
||||
|
||||
|
|
@ -105,6 +117,14 @@ public final class UpdateOAuthProviderCmd extends BaseCmd {
|
|||
return enabled;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public String getDomainPath() {
|
||||
return domainPath;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -128,9 +148,10 @@ public final class UpdateOAuthProviderCmd extends BaseCmd {
|
|||
public void execute() {
|
||||
OauthProviderVO result = _oauthMgr.updateOauthProvider(this);
|
||||
if (result != null) {
|
||||
Domain domain = result.getDomainId() != null ? ApiDBUtils.findDomainById(result.getDomainId()) : null;
|
||||
OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(),
|
||||
result.getAuthorizeUrl(), result.getTokenUrl());
|
||||
result.getAuthorizeUrl(), result.getTokenUrl(), domain);
|
||||
|
||||
List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauthMgr.listUserOAuth2AuthenticationProviders();
|
||||
List<String> authenticatorPluginNames = new ArrayList<>();
|
||||
|
|
@ -138,7 +159,8 @@ public final class UpdateOAuthProviderCmd extends BaseCmd {
|
|||
String name = authenticator.getName();
|
||||
authenticatorPluginNames.add(name);
|
||||
}
|
||||
if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
|
||||
boolean oauthEnabled = OAuth2AuthManager.isPluginEnabledForDomain(result.getDomainId());
|
||||
if (oauthEnabled && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
|
||||
r.setEnabled(true);
|
||||
} else {
|
||||
r.setEnabled(false);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import java.net.InetAddress;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.user.Account;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
|
@ -34,13 +37,11 @@ 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.DomainResponse;
|
||||
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 com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.user.Account;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
@APICommand(name = "verifyOAuthCodeAndGetUser", description = "Verify the OAuth Code and fetch the corresponding user from provider", responseObject = OauthProviderResponse.class, entityType = {},
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
|
|
@ -58,6 +59,14 @@ public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuth
|
|||
@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;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
|
||||
description = "Domain ID for domain-specific OAuth provider lookup. If not provided, uses global provider", since = "4.23.0")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN, type = CommandType.STRING,
|
||||
description = "Domain path for domain-specific OAuth provider lookup. Ignored when Domain ID is passed.", since = "4.23.0")
|
||||
private String domainPath;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -70,6 +79,10 @@ public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuth
|
|||
return secretCode;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -97,8 +110,9 @@ public class VerifyOAuthCodeAndGetUserCmd extends BaseListCmd implements APIAuth
|
|||
if (ArrayUtils.isNotEmpty(providerArray)) {
|
||||
provider = providerArray[0];
|
||||
}
|
||||
domainId = _oauth2mgr.resolveDomainId(params);
|
||||
|
||||
String email = _oauth2mgr.verifyCodeAndFetchEmail(secretCode, provider);
|
||||
String email = _oauth2mgr.verifySecretCodeAndFetchEmail(secretCode, provider, domainId);
|
||||
if (email != null) {
|
||||
UserResponse response = new UserResponse();
|
||||
response.setEmail(email);
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.oauth2.api.response;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
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;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
|
|
@ -55,6 +58,18 @@ public class OauthProviderResponse extends BaseResponse {
|
|||
@Param(description = "Redirect URI registered in the OAuth provider")
|
||||
private String redirectUri;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_ID)
|
||||
@Param(description = "UUID of the domain the provider belongs to (empty for global)", since = "4.23.0")
|
||||
private String domainUuid;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN)
|
||||
@Param(description = "name of the domain the provider belongs to (empty for global)", since = "4.23.0")
|
||||
private String domainName;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_PATH)
|
||||
@Param(description = "path of the domain the provider belongs to (empty for global)", since = "4.23.0")
|
||||
private String domainPath;
|
||||
|
||||
@SerializedName(ApiConstants.AUTHORIZE_URL)
|
||||
@Param(description = "Authorize URL registered in the OAuth provider")
|
||||
private String authorizeUrl;
|
||||
|
|
@ -67,7 +82,7 @@ public class OauthProviderResponse extends BaseResponse {
|
|||
@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, String authorizeUrl, String tokenUrl) {
|
||||
public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri, String authorizeUrl, String tokenUrl, Domain domain) {
|
||||
this.id = id;
|
||||
this.provider = provider;
|
||||
this.name = provider;
|
||||
|
|
@ -77,6 +92,19 @@ public class OauthProviderResponse extends BaseResponse {
|
|||
this.redirectUri = redirectUri;
|
||||
this.authorizeUrl = authorizeUrl;
|
||||
this.tokenUrl = tokenUrl;
|
||||
if (Objects.nonNull(domain)) {
|
||||
this.domainUuid = domain.getUuid();
|
||||
this.domainName = domain.getName();
|
||||
this.domainPath = prettifyDomainPath(domain.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private static String prettifyDomainPath(String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
|
||||
return "ROOT" + trimmed;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
|
@ -128,6 +156,30 @@ public class OauthProviderResponse extends BaseResponse {
|
|||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getDomainUuid() {
|
||||
return domainUuid;
|
||||
}
|
||||
|
||||
public void setDomainUuid(String domainUuid) {
|
||||
this.domainUuid = domainUuid;
|
||||
}
|
||||
|
||||
public String getDomainName() {
|
||||
return domainName;
|
||||
}
|
||||
|
||||
public void setDomainName(String domainName) {
|
||||
this.domainName = domainName;
|
||||
}
|
||||
|
||||
public String getDomainPath() {
|
||||
return domainPath;
|
||||
}
|
||||
|
||||
public void setDomainPath(String domainPath) {
|
||||
this.domainPath = domainPath;
|
||||
}
|
||||
|
||||
public String getAuthorizeUrl() {
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,16 @@ package org.apache.cloudstack.oauth2.dao;
|
|||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OauthProviderDao extends GenericDao<OauthProviderVO, Long> {
|
||||
|
||||
public OauthProviderVO findByProvider(String provider);
|
||||
public OauthProviderVO findByProviderAndDomain(String provider, Long domainId);
|
||||
|
||||
public List<OauthProviderVO> listByDomainIncludingGlobal(Long domainId);
|
||||
|
||||
public List<OauthProviderVO> listByDomain(Long domainId);
|
||||
|
||||
public OauthProviderVO findByProviderAndDomainWithGlobalFallback(String provider, Long domainId);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,23 +22,54 @@ import com.cloud.utils.db.SearchBuilder;
|
|||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class OauthProviderDaoImpl extends GenericDaoBase<OauthProviderVO, Long> implements OauthProviderDao {
|
||||
|
||||
private final SearchBuilder<OauthProviderVO> oauthProviderSearchByName;
|
||||
private final SearchBuilder<OauthProviderVO> oauthProviderSearchByProviderAndDomain;
|
||||
|
||||
public OauthProviderDaoImpl() {
|
||||
super();
|
||||
|
||||
oauthProviderSearchByName = createSearchBuilder();
|
||||
oauthProviderSearchByName.and("provider", oauthProviderSearchByName.entity().getProvider(), SearchCriteria.Op.EQ);
|
||||
oauthProviderSearchByName.done();
|
||||
oauthProviderSearchByProviderAndDomain = createSearchBuilder();
|
||||
oauthProviderSearchByProviderAndDomain.and("provider", oauthProviderSearchByProviderAndDomain.entity().getProvider(), SearchCriteria.Op.EQ);
|
||||
oauthProviderSearchByProviderAndDomain.and("domainId", oauthProviderSearchByProviderAndDomain.entity().getDomainId(), SearchCriteria.Op.EQ);
|
||||
oauthProviderSearchByProviderAndDomain.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OauthProviderVO findByProvider(String provider) {
|
||||
SearchCriteria<OauthProviderVO> sc = oauthProviderSearchByName.create();
|
||||
public OauthProviderVO findByProviderAndDomain(String provider, Long domainId) {
|
||||
SearchCriteria<OauthProviderVO> sc = oauthProviderSearchByProviderAndDomain.create();
|
||||
sc.setParameters("provider", provider);
|
||||
|
||||
sc.setParameters("domainId", domainId);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OauthProviderVO> listByDomainIncludingGlobal(Long domainId) {
|
||||
SearchCriteria<OauthProviderVO> sc = createSearchCriteria();
|
||||
sc.addOr("domainId", SearchCriteria.Op.EQ, domainId);
|
||||
sc.addOr("domainId", SearchCriteria.Op.NULL);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OauthProviderVO> listByDomain(Long domainId) {
|
||||
SearchCriteria<OauthProviderVO> sc = createSearchCriteria();
|
||||
sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OauthProviderVO findByProviderAndDomainWithGlobalFallback(String provider, Long domainId) {
|
||||
OauthProviderVO providerVO = null;
|
||||
if (Objects.nonNull(domainId)) {
|
||||
providerVO = findByProviderAndDomain(provider, domainId);
|
||||
}
|
||||
if (Objects.isNull(providerVO)) {
|
||||
providerVO = findByProviderAndDomain(provider, null);
|
||||
}
|
||||
return providerVO;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,17 +56,27 @@ public class GithubOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode) {
|
||||
return verifyUser(email, secretCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifySecretCodeAndFetchEmail(String secretCode) {
|
||||
return verifySecretCodeAndFetchEmail(secretCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode, Long domainId) {
|
||||
if (StringUtils.isAnyEmpty(email, secretCode)) {
|
||||
throw new CloudRuntimeException(String.format("Either email or secretcode should not be null/empty"));
|
||||
}
|
||||
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName());
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
|
||||
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)) {
|
||||
String verifiedEmail = verifySecretCodeAndFetchEmail(secretCode, domainId);
|
||||
if (StringUtils.isEmpty(verifiedEmail) || !email.equals(verifiedEmail)) {
|
||||
throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
|
||||
}
|
||||
|
||||
|
|
@ -76,16 +86,19 @@ public class GithubOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String secretCode) {
|
||||
String accessToken = getAccessToken(secretCode);
|
||||
if (accessToken == null) {
|
||||
public String verifySecretCodeAndFetchEmail(String secretCode, Long domainId) {
|
||||
String accessToken = getAccessToken(secretCode, domainId);
|
||||
if (StringUtils.isEmpty(accessToken)) {
|
||||
return null;
|
||||
}
|
||||
return getUserEmailAddress();
|
||||
}
|
||||
|
||||
protected String getAccessToken(String secretCode) throws CloudRuntimeException {
|
||||
OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName());
|
||||
protected String getAccessToken(String secretCode, Long domainId) throws CloudRuntimeException {
|
||||
if (StringUtils.isNotEmpty(accessToken)) {
|
||||
return accessToken;
|
||||
}
|
||||
OauthProviderVO githubProvider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
|
||||
String tokenUrl = "https://github.com/login/oauth/access_token";
|
||||
String generatedAccessToken = null;
|
||||
try {
|
||||
|
|
@ -131,7 +144,7 @@ public class GithubOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
}
|
||||
|
||||
public String getUserEmailAddress() throws CloudRuntimeException {
|
||||
if (accessToken == null) {
|
||||
if (StringUtils.isEmpty(accessToken)) {
|
||||
throw new CloudRuntimeException("Access Token not found to fetch the email address");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,16 +60,36 @@ public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode) {
|
||||
return verifyUser(email, secretCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifySecretCodeAndFetchEmail(String secretCode) {
|
||||
return verifySecretCodeAndFetchEmail(secretCode, null);
|
||||
}
|
||||
|
||||
protected void clearAccessAndRefreshTokens() {
|
||||
accessToken = null;
|
||||
refreshToken = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserEmailAddress() throws CloudRuntimeException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode, Long domainId) {
|
||||
if (StringUtils.isAnyEmpty(email, secretCode)) {
|
||||
throw new CloudAuthenticationException("Either email or secret code should not be null/empty");
|
||||
}
|
||||
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProvider(getName());
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
|
||||
if (providerVO == null) {
|
||||
throw new CloudAuthenticationException("Google provider is not registered, so user cannot be verified");
|
||||
}
|
||||
|
||||
String verifiedEmail = verifyCodeAndFetchEmail(secretCode);
|
||||
String verifiedEmail = verifySecretCodeAndFetchEmail(secretCode, domainId);
|
||||
if (verifiedEmail == null || !email.equals(verifiedEmail)) {
|
||||
throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
|
||||
}
|
||||
|
|
@ -79,11 +99,11 @@ public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String secretCode) {
|
||||
OauthProviderVO googleProvider = _oauthProviderDao.findByProvider(getName());
|
||||
String clientId = googleProvider.getClientId();
|
||||
String secret = googleProvider.getSecretKey();
|
||||
String redirectURI = googleProvider.getRedirectUri();
|
||||
public String verifySecretCodeAndFetchEmail(String secretCode, Long domainId) {
|
||||
OauthProviderVO provider = _oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
|
||||
String clientId = provider.getClientId();
|
||||
String secret = provider.getSecretKey();
|
||||
String redirectURI = provider.getRedirectUri();
|
||||
GoogleClientSecrets clientSecrets = new GoogleClientSecrets()
|
||||
.setWeb(new GoogleClientSecrets.Details()
|
||||
.setClientId(clientId)
|
||||
|
|
@ -129,13 +149,4 @@ public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
return userinfo.getEmail();
|
||||
}
|
||||
|
||||
protected void clearAccessAndRefreshTokens() {
|
||||
accessToken = null;
|
||||
refreshToken = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserEmailAddress() throws CloudRuntimeException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,16 +81,21 @@ public class KeycloakOAuth2Provider extends AdapterBase implements UserOAuth2Aut
|
|||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode) {
|
||||
return verifyUser(email, secretCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode, Long domainId) {
|
||||
if (StringUtils.isAnyEmpty(email, secretCode)) {
|
||||
throw new CloudAuthenticationException("Either email or secret code should not be null/empty");
|
||||
}
|
||||
|
||||
OauthProviderVO providerVO = oauthProviderDao.findByProvider(getName());
|
||||
OauthProviderVO providerVO = oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
|
||||
if (providerVO == null) {
|
||||
throw new CloudAuthenticationException("Keycloak provider is not registered, so user cannot be verified");
|
||||
}
|
||||
|
||||
String verifiedEmail = verifyCodeAndFetchEmail(secretCode);
|
||||
String verifiedEmail = verifySecretCodeAndFetchEmail(secretCode, domainId);
|
||||
if (StringUtils.isBlank(verifiedEmail) || !email.equals(verifiedEmail)) {
|
||||
throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
|
||||
}
|
||||
|
|
@ -100,8 +105,13 @@ public class KeycloakOAuth2Provider extends AdapterBase implements UserOAuth2Aut
|
|||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String secretCode) {
|
||||
OauthProviderVO provider = oauthProviderDao.findByProvider(getName());
|
||||
public String verifySecretCodeAndFetchEmail(String secretCode) {
|
||||
return verifySecretCodeAndFetchEmail(secretCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifySecretCodeAndFetchEmail(String secretCode, Long domainId) {
|
||||
OauthProviderVO provider = oauthProviderDao.findByProviderAndDomainWithGlobalFallback(getName(), domainId);
|
||||
if (provider == null) {
|
||||
throw new CloudAuthenticationException("Keycloak provider is not registered, so user cannot be verified");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ public class OauthProviderVO implements Identity, InternalIdentity {
|
|||
@Column(name = "redirect_uri")
|
||||
private String redirectUri;
|
||||
|
||||
@Column(name = "domain_id")
|
||||
private Long domainId;
|
||||
|
||||
@Column(name = "authorize_url")
|
||||
private String authorizeUrl;
|
||||
|
||||
|
|
@ -142,6 +145,14 @@ public class OauthProviderVO implements Identity, InternalIdentity {
|
|||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public void setDomainId(Long domainId) {
|
||||
this.domainId = domainId;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,13 @@
|
|||
|
||||
package org.apache.cloudstack.oauth2;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.domain.DomainVO;
|
||||
import com.cloud.user.DomainService;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.framework.messagebus.MessageBus;
|
||||
import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
|
||||
import org.apache.cloudstack.oauth2.api.command.DeleteOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.RegisterOAuthProviderCmd;
|
||||
import org.apache.cloudstack.oauth2.api.command.UpdateOAuthProviderCmd;
|
||||
|
|
@ -36,12 +42,17 @@ import org.mockito.MockitoAnnotations;
|
|||
import org.mockito.Spy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class OAuth2AuthManagerImplTest {
|
||||
|
|
@ -53,6 +64,12 @@ public class OAuth2AuthManagerImplTest {
|
|||
@Mock
|
||||
OauthProviderDao _oauthProviderDao;
|
||||
|
||||
@Mock
|
||||
DomainService _domainService;
|
||||
|
||||
@Mock
|
||||
MessageBus _messageBus;
|
||||
|
||||
AutoCloseable closeable;
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
|
@ -66,7 +83,7 @@ public class OAuth2AuthManagerImplTest {
|
|||
|
||||
@Test
|
||||
public void testRegisterOauthProvider() {
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(false);
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(false);
|
||||
RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
try {
|
||||
_authManager.registerOauthProvider(cmd);
|
||||
|
|
@ -76,25 +93,27 @@ public class OAuth2AuthManagerImplTest {
|
|||
}
|
||||
|
||||
// Test when provider is already registered
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(true);
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true);
|
||||
OauthProviderVO providerVO = new OauthProviderVO();
|
||||
providerVO.setProvider("testProvider");
|
||||
when(_authManager._oauthProviderDao.findByProvider(Mockito.anyString())).thenReturn(providerVO);
|
||||
when(_authManager._oauthProviderDao.findByProviderAndDomain(Mockito.anyString(), Mockito.isNull())).thenReturn(providerVO);
|
||||
when(cmd.getProvider()).thenReturn("testProvider");
|
||||
when(cmd.getDomainId()).thenReturn(null);
|
||||
|
||||
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());
|
||||
assertEquals("Global 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);
|
||||
when(_authManager._oauthProviderDao.findByProviderAndDomain(Mockito.anyString(), Mockito.isNull())).thenReturn(providerVO);
|
||||
OauthProviderVO savedProviderVO = new OauthProviderVO();
|
||||
when(cmd.getProvider()).thenReturn("github");
|
||||
when(cmd.getDomainId()).thenReturn(null);
|
||||
when(_authManager._oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenReturn(savedProviderVO);
|
||||
OauthProviderVO result = _authManager.registerOauthProvider(cmd);
|
||||
assertEquals("github", result.getProvider());
|
||||
|
|
@ -140,6 +159,115 @@ public class OAuth2AuthManagerImplTest {
|
|||
assertEquals(secretKey, result.getSecretKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOauthProviderReassignsDomain() {
|
||||
Long id = 5L;
|
||||
Long oldDomainId = 10L;
|
||||
Long newDomainId = 20L;
|
||||
|
||||
UpdateOAuthProviderCmd cmd = Mockito.mock(UpdateOAuthProviderCmd.class);
|
||||
when(cmd.getId()).thenReturn(id);
|
||||
when(cmd.getDomainId()).thenReturn(newDomainId);
|
||||
|
||||
OauthProviderVO providerVO = new OauthProviderVO();
|
||||
providerVO.setProvider("github");
|
||||
providerVO.setDomainId(oldDomainId);
|
||||
when(_oauthProviderDao.findById(id)).thenReturn(providerVO);
|
||||
|
||||
Domain newDomain = Mockito.mock(Domain.class);
|
||||
when(newDomain.getId()).thenReturn(newDomainId);
|
||||
when(_domainService.getDomain(Mockito.anyString())).thenReturn(newDomain);
|
||||
Mockito.doReturn(newDomainId).when(_authManager).resolveDomainIdFromIdOrPath(newDomainId, null);
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", newDomainId)).thenReturn(null);
|
||||
when(_oauthProviderDao.update(Mockito.eq(id), Mockito.any(OauthProviderVO.class))).thenReturn(true);
|
||||
when(_oauthProviderDao.findById(id)).thenReturn(providerVO);
|
||||
|
||||
OauthProviderVO result = _authManager.updateOauthProvider(cmd);
|
||||
assertEquals(newDomainId, result.getDomainId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOauthProviderRejectsDuplicateAtTargetDomain() {
|
||||
Long id = 5L;
|
||||
Long oldDomainId = 10L;
|
||||
Long newDomainId = 20L;
|
||||
|
||||
UpdateOAuthProviderCmd cmd = Mockito.mock(UpdateOAuthProviderCmd.class);
|
||||
when(cmd.getId()).thenReturn(id);
|
||||
when(cmd.getDomainId()).thenReturn(newDomainId);
|
||||
|
||||
OauthProviderVO providerVO = new OauthProviderVO();
|
||||
providerVO.setProvider("github");
|
||||
providerVO.setDomainId(oldDomainId);
|
||||
when(_oauthProviderDao.findById(id)).thenReturn(providerVO);
|
||||
|
||||
Mockito.doReturn(newDomainId).when(_authManager).resolveDomainIdFromIdOrPath(newDomainId, null);
|
||||
OauthProviderVO collision = new OauthProviderVO();
|
||||
collision.setProvider("github");
|
||||
collision.setDomainId(newDomainId);
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", newDomainId)).thenReturn(collision);
|
||||
|
||||
try {
|
||||
_authManager.updateOauthProvider(cmd);
|
||||
Assert.fail("Expected CloudRuntimeException for duplicate at target domain");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertTrue(e.getMessage().contains("already registered"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterOauthProviderForRootDomainTreatedAsGlobal() {
|
||||
RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(cmd.getProvider()).thenReturn("github");
|
||||
when(cmd.getDomainId()).thenReturn(com.cloud.domain.Domain.ROOT_DOMAIN);
|
||||
when(cmd.getSecretKey()).thenReturn("secret");
|
||||
when(cmd.getClientId()).thenReturn("clientId");
|
||||
when(cmd.getRedirectUri()).thenReturn("https://redirect");
|
||||
|
||||
// global check must be consulted (domainId resolves to null), not the ROOT domain scope
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.isNull())).thenReturn(true);
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", null)).thenReturn(null);
|
||||
when(_oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
OauthProviderVO result = _authManager.registerOauthProvider(cmd);
|
||||
assertNull(result.getDomainId());
|
||||
Mockito.verify(_oauthProviderDao).findByProviderAndDomain("github", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalizeGlobalScopeMapsRootToNull() {
|
||||
assertNull(_authManager.normalizeGlobalScope(com.cloud.domain.Domain.ROOT_DOMAIN));
|
||||
assertNull(_authManager.normalizeGlobalScope(null));
|
||||
assertEquals(Long.valueOf(42L), _authManager.normalizeGlobalScope(42L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOauthProviderRejectsEnableWhenPluginDisabledAtScope() {
|
||||
Long id = 7L;
|
||||
Long domainId = 42L;
|
||||
|
||||
UpdateOAuthProviderCmd cmd = Mockito.mock(UpdateOAuthProviderCmd.class);
|
||||
when(cmd.getId()).thenReturn(id);
|
||||
when(cmd.getEnabled()).thenReturn(true);
|
||||
|
||||
OauthProviderVO providerVO = new OauthProviderVO();
|
||||
providerVO.setProvider("github");
|
||||
providerVO.setDomainId(domainId);
|
||||
providerVO.setEnabled(false);
|
||||
|
||||
when(_oauthProviderDao.findById(id)).thenReturn(providerVO);
|
||||
Mockito.doReturn(false).when(_authManager).isOAuthPluginEnabled(domainId);
|
||||
|
||||
try {
|
||||
_authManager.updateOauthProvider(cmd);
|
||||
Assert.fail("Expected CloudRuntimeException when enabling provider while oauth2.enabled is false at scope");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertTrue(e.getMessage().contains("OAuth plugin is not enabled"));
|
||||
}
|
||||
|
||||
Mockito.verify(_oauthProviderDao, Mockito.never()).update(Mockito.eq(id), Mockito.any(OauthProviderVO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListOauthProviders() {
|
||||
String uuid = "1234-5678-9101";
|
||||
|
|
@ -150,20 +278,32 @@ public class OAuth2AuthManagerImplTest {
|
|||
|
||||
// Test when uuid is not null
|
||||
when(_oauthProviderDao.findByUuid(uuid)).thenReturn(providerVO);
|
||||
List<OauthProviderVO> result = _authManager.listOauthProviders(null, uuid);
|
||||
List<OauthProviderVO> result = _authManager.listOauthProviders(null, uuid, null);
|
||||
assertEquals(providerList, result);
|
||||
|
||||
// Test when provider is not blank
|
||||
when(_oauthProviderDao.findByProvider(provider)).thenReturn(providerVO);
|
||||
result = _authManager.listOauthProviders(provider, null);
|
||||
when(_oauthProviderDao.findByProviderAndDomain(provider, null)).thenReturn(providerVO);
|
||||
result = _authManager.listOauthProviders(provider, null, null);
|
||||
assertEquals(providerList, result);
|
||||
|
||||
// Test when both uuid and provider are null
|
||||
when(_oauthProviderDao.listAll()).thenReturn(providerList);
|
||||
result = _authManager.listOauthProviders(null, null);
|
||||
result = _authManager.listOauthProviders(null, null, null);
|
||||
assertEquals(providerList, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteOauthProviderHardDeletes() {
|
||||
Long providerId = 42L;
|
||||
when(_oauthProviderDao.expunge(providerId)).thenReturn(true);
|
||||
|
||||
boolean result = _authManager.deleteOauthProvider(providerId);
|
||||
|
||||
assertTrue(result);
|
||||
Mockito.verify(_oauthProviderDao).expunge(providerId);
|
||||
Mockito.verify(_oauthProviderDao, Mockito.never()).remove(Mockito.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommands() {
|
||||
List<Class<?>> expectedCmdList = new ArrayList<>();
|
||||
|
|
@ -178,14 +318,362 @@ public class OAuth2AuthManagerImplTest {
|
|||
|
||||
@Test
|
||||
public void testStart() {
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(true);
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true);
|
||||
doNothing().when(_authManager).initializeUserOAuth2AuthenticationProvidersMap();
|
||||
boolean result = _authManager.start();
|
||||
assertTrue(result);
|
||||
|
||||
when(_authManager.isOAuthPluginEnabled()).thenReturn(false);
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(false);
|
||||
result = _authManager.start();
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterOauthProviderWithDomain() {
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true);
|
||||
RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(cmd.getProvider()).thenReturn("github");
|
||||
when(cmd.getDomainId()).thenReturn(5L);
|
||||
when(cmd.getSecretKey()).thenReturn("secret");
|
||||
when(cmd.getClientId()).thenReturn("clientId");
|
||||
when(cmd.getRedirectUri()).thenReturn("https://redirect");
|
||||
|
||||
// No existing provider for this domain
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", 5L)).thenReturn(null);
|
||||
when(_oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
OauthProviderVO result = _authManager.registerOauthProvider(cmd);
|
||||
assertEquals("github", result.getProvider());
|
||||
assertEquals(Long.valueOf(5L), result.getDomainId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterOauthProviderDuplicateForDomain() {
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true);
|
||||
RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(cmd.getProvider()).thenReturn("github");
|
||||
when(cmd.getDomainId()).thenReturn(5L);
|
||||
|
||||
OauthProviderVO existing = new OauthProviderVO();
|
||||
existing.setProvider("github");
|
||||
existing.setDomainId(5L);
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", 5L)).thenReturn(existing);
|
||||
|
||||
try {
|
||||
_authManager.registerOauthProvider(cmd);
|
||||
Assert.fail("Expected CloudRuntimeException was not thrown");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertEquals("Provider with the name github is already registered for domain 5", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListOauthProvidersWithDomainId() {
|
||||
Long domainId = 5L;
|
||||
OauthProviderVO globalProvider = new OauthProviderVO();
|
||||
globalProvider.setProvider("google");
|
||||
OauthProviderVO domainProvider = new OauthProviderVO();
|
||||
domainProvider.setProvider("github");
|
||||
domainProvider.setDomainId(domainId);
|
||||
List<OauthProviderVO> providers = Arrays.asList(globalProvider, domainProvider);
|
||||
|
||||
when(_oauthProviderDao.listByDomainIncludingGlobal(domainId)).thenReturn(providers);
|
||||
List<OauthProviderVO> result = _authManager.listOauthProviders(null, null, domainId);
|
||||
assertEquals(2, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListOauthProvidersByProviderAndDomain() {
|
||||
Long domainId = 5L;
|
||||
OauthProviderVO domainProvider = new OauthProviderVO();
|
||||
domainProvider.setProvider("github");
|
||||
domainProvider.setDomainId(domainId);
|
||||
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", domainId)).thenReturn(domainProvider);
|
||||
List<OauthProviderVO> result = _authManager.listOauthProviders("github", null, domainId);
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("github", result.get(0).getProvider());
|
||||
assertEquals(Long.valueOf(5L), result.get(0).getDomainId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdFromDomainUuid() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN_ID, new String[]{"test-uuid-123"});
|
||||
|
||||
Domain domain = Mockito.mock(Domain.class);
|
||||
when(domain.getId()).thenReturn(10L);
|
||||
when(_domainService.getDomain("test-uuid-123")).thenReturn(domain);
|
||||
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertEquals(Long.valueOf(10L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdGlobalFilter() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN_ID, new String[]{"-1"});
|
||||
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertEquals(Long.valueOf(-1L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdFromDomainPath() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN, new String[]{"ROOT/child"});
|
||||
|
||||
Domain domain = Mockito.mock(Domain.class);
|
||||
when(domain.getId()).thenReturn(20L);
|
||||
when(_domainService.findDomainByIdOrPath(null, "/ROOT/child/")).thenReturn(domain);
|
||||
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertEquals(Long.valueOf(20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdFromDomainPathWithSlashes() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN, new String[]{"/ROOT/child/"});
|
||||
|
||||
Domain domain = Mockito.mock(Domain.class);
|
||||
when(domain.getId()).thenReturn(20L);
|
||||
when(_domainService.findDomainByIdOrPath(null, "/ROOT/child/")).thenReturn(domain);
|
||||
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertEquals(Long.valueOf(20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdReturnsNullWhenNotFound() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN_ID, new String[]{"nonexistent-uuid"});
|
||||
|
||||
when(_domainService.getDomain("nonexistent-uuid")).thenReturn(null);
|
||||
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdReturnsNullForEmptyParams() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdPrefersUuidOverPath() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN_ID, new String[]{"test-uuid"});
|
||||
params.put(ApiConstants.DOMAIN, new String[]{"/ROOT/child/"});
|
||||
|
||||
Domain domain = Mockito.mock(Domain.class);
|
||||
when(domain.getId()).thenReturn(10L);
|
||||
when(_domainService.getDomain("test-uuid")).thenReturn(domain);
|
||||
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertEquals(Long.valueOf(10L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveDomainIdFallsBackToPathWhenUuidNotFound() {
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
params.put(ApiConstants.DOMAIN_ID, new String[]{"bad-uuid"});
|
||||
params.put(ApiConstants.DOMAIN, new String[]{"/ROOT/"});
|
||||
|
||||
when(_domainService.getDomain("bad-uuid")).thenReturn(null);
|
||||
Domain domain = Mockito.mock(Domain.class);
|
||||
when(domain.getId()).thenReturn(1L);
|
||||
when(_domainService.findDomainByIdOrPath(null, "/ROOT/")).thenReturn(domain);
|
||||
|
||||
Long result = _authManager.resolveDomainId(params);
|
||||
assertEquals(Long.valueOf(1L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateOauthProviderNotFound() {
|
||||
UpdateOAuthProviderCmd cmd = Mockito.mock(UpdateOAuthProviderCmd.class);
|
||||
when(cmd.getId()).thenReturn(999L);
|
||||
when(_oauthProviderDao.findById(999L)).thenReturn(null);
|
||||
|
||||
try {
|
||||
_authManager.updateOauthProvider(cmd);
|
||||
Assert.fail("Expected CloudRuntimeException was not thrown");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertEquals("Provider with the given id is not there", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserOAuth2AuthenticationProviderEmptyName() {
|
||||
try {
|
||||
_authManager.getUserOAuth2AuthenticationProvider("");
|
||||
Assert.fail("Expected CloudRuntimeException was not thrown");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertEquals("OAuth2 authentication provider name is empty", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserOAuth2AuthenticationProviderNotFound() {
|
||||
try {
|
||||
_authManager.getUserOAuth2AuthenticationProvider("nonexistent");
|
||||
Assert.fail("Expected CloudRuntimeException was not thrown");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertTrue(e.getMessage().contains("nonexistent"));
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple-domain OAuth tests
|
||||
|
||||
@Test
|
||||
public void testSameProviderRegisteredInTwoDifferentDomains() {
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true);
|
||||
|
||||
// Register github for domain 5
|
||||
RegisterOAuthProviderCmd cmd1 = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(cmd1.getProvider()).thenReturn("github");
|
||||
when(cmd1.getDomainId()).thenReturn(5L);
|
||||
when(cmd1.getSecretKey()).thenReturn("secret1");
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", 5L)).thenReturn(null);
|
||||
when(_oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
OauthProviderVO result1 = _authManager.registerOauthProvider(cmd1);
|
||||
assertEquals("github", result1.getProvider());
|
||||
assertEquals(Long.valueOf(5L), result1.getDomainId());
|
||||
|
||||
// Register github for domain 10 — should succeed independently
|
||||
RegisterOAuthProviderCmd cmd2 = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(cmd2.getProvider()).thenReturn("github");
|
||||
when(cmd2.getDomainId()).thenReturn(10L);
|
||||
when(cmd2.getSecretKey()).thenReturn("secret2");
|
||||
when(_oauthProviderDao.findByProviderAndDomain("github", 10L)).thenReturn(null);
|
||||
|
||||
OauthProviderVO result2 = _authManager.registerOauthProvider(cmd2);
|
||||
assertEquals("github", result2.getProvider());
|
||||
assertEquals(Long.valueOf(10L), result2.getDomainId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameProviderRegisteredGloballyAndForDomain() {
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true);
|
||||
|
||||
// Global registration (domainId = null)
|
||||
RegisterOAuthProviderCmd globalCmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(globalCmd.getProvider()).thenReturn("google");
|
||||
when(globalCmd.getDomainId()).thenReturn(null);
|
||||
when(_oauthProviderDao.findByProviderAndDomain("google", null)).thenReturn(null);
|
||||
when(_oauthProviderDao.persist(Mockito.any(OauthProviderVO.class))).thenAnswer(i -> i.getArgument(0));
|
||||
|
||||
OauthProviderVO globalResult = _authManager.registerOauthProvider(globalCmd);
|
||||
assertNull(globalResult.getDomainId());
|
||||
|
||||
// Domain-specific registration for same provider — should succeed (different scope)
|
||||
RegisterOAuthProviderCmd domainCmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(domainCmd.getProvider()).thenReturn("google");
|
||||
when(domainCmd.getDomainId()).thenReturn(7L);
|
||||
when(_oauthProviderDao.findByProviderAndDomain("google", 7L)).thenReturn(null);
|
||||
|
||||
OauthProviderVO domainResult = _authManager.registerOauthProvider(domainCmd);
|
||||
assertEquals(Long.valueOf(7L), domainResult.getDomainId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListOauthProvidersForDomainIncludesGlobalProviders() {
|
||||
Long domainId = 5L;
|
||||
OauthProviderVO globalGoogle = new OauthProviderVO();
|
||||
globalGoogle.setProvider("google");
|
||||
// domainId is null — global
|
||||
|
||||
OauthProviderVO domainGithub = new OauthProviderVO();
|
||||
domainGithub.setProvider("github");
|
||||
domainGithub.setDomainId(domainId);
|
||||
|
||||
OauthProviderVO otherDomainGoogle = new OauthProviderVO();
|
||||
otherDomainGoogle.setProvider("google");
|
||||
otherDomainGoogle.setDomainId(10L);
|
||||
|
||||
// listByDomainIncludingGlobal returns providers for domain 5 + global (not domain 10)
|
||||
when(_oauthProviderDao.listByDomainIncludingGlobal(domainId))
|
||||
.thenReturn(Arrays.asList(globalGoogle, domainGithub));
|
||||
|
||||
List<OauthProviderVO> result = _authManager.listOauthProviders(null, null, domainId);
|
||||
assertEquals(2, result.size());
|
||||
assertTrue(result.stream().anyMatch(p -> p.getDomainId() == null)); // global included
|
||||
assertTrue(result.stream().anyMatch(p -> Long.valueOf(5L).equals(p.getDomainId()))); // domain-specific included
|
||||
assertTrue(result.stream().noneMatch(p -> Long.valueOf(10L).equals(p.getDomainId()))); // other domain excluded
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListAllProvidersAcrossAllDomains() {
|
||||
OauthProviderVO global = new OauthProviderVO();
|
||||
global.setProvider("google");
|
||||
|
||||
OauthProviderVO domain5 = new OauthProviderVO();
|
||||
domain5.setProvider("github");
|
||||
domain5.setDomainId(5L);
|
||||
|
||||
OauthProviderVO domain10 = new OauthProviderVO();
|
||||
domain10.setProvider("google");
|
||||
domain10.setDomainId(10L);
|
||||
|
||||
when(_oauthProviderDao.listAll()).thenReturn(Arrays.asList(global, domain5, domain10));
|
||||
|
||||
List<OauthProviderVO> result = _authManager.listOauthProviders(null, null, null);
|
||||
assertEquals(3, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateGlobalProviderRejected() {
|
||||
when(_authManager.isOAuthPluginEnabled(Mockito.nullable(Long.class))).thenReturn(true);
|
||||
RegisterOAuthProviderCmd cmd = Mockito.mock(RegisterOAuthProviderCmd.class);
|
||||
when(cmd.getProvider()).thenReturn("google");
|
||||
when(cmd.getDomainId()).thenReturn(null);
|
||||
|
||||
OauthProviderVO existing = new OauthProviderVO();
|
||||
existing.setProvider("google");
|
||||
when(_oauthProviderDao.findByProviderAndDomain("google", null)).thenReturn(existing);
|
||||
|
||||
try {
|
||||
_authManager.registerOauthProvider(cmd);
|
||||
Assert.fail("Expected CloudRuntimeException was not thrown");
|
||||
} catch (CloudRuntimeException e) {
|
||||
assertEquals("Global provider with the name google is already registered", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDomainDeletionCleansUpOAuthProviders() {
|
||||
Long domainId = 42L;
|
||||
|
||||
OauthProviderVO provider1 = new OauthProviderVO();
|
||||
provider1.setProvider("github");
|
||||
provider1.setDomainId(domainId);
|
||||
|
||||
OauthProviderVO provider2 = new OauthProviderVO();
|
||||
provider2.setProvider("google");
|
||||
provider2.setDomainId(domainId);
|
||||
|
||||
when(_oauthProviderDao.listByDomain(domainId)).thenReturn(Arrays.asList(provider1, provider2));
|
||||
when(_oauthProviderDao.expunge(Mockito.anyLong())).thenReturn(true);
|
||||
|
||||
// Capture the subscriber registered during start()
|
||||
doNothing().when(_authManager).initializeUserOAuth2AuthenticationProvidersMap();
|
||||
Mockito.doAnswer(invocation -> {
|
||||
String subject = invocation.getArgument(0);
|
||||
MessageSubscriber subscriber = invocation.getArgument(1);
|
||||
// Simulate domain removal event
|
||||
DomainVO domain = Mockito.mock(DomainVO.class);
|
||||
when(domain.getId()).thenReturn(domainId);
|
||||
subscriber.onPublishMessage("", subject, domain);
|
||||
return null;
|
||||
}).when(_messageBus).subscribe(Mockito.eq(com.cloud.user.DomainManager.MESSAGE_PRE_REMOVE_DOMAIN_EVENT), Mockito.any());
|
||||
|
||||
_authManager.start();
|
||||
|
||||
verify(_oauthProviderDao).listByDomain(domainId);
|
||||
verify(_oauthProviderDao, Mockito.times(2)).expunge(Mockito.anyLong());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class OAuth2UserAuthenticatorTest {
|
|||
@Before
|
||||
public void setUp() {
|
||||
closeable = MockitoAnnotations.openMocks(this);
|
||||
doReturn(true).when(authenticator).isOAuthPluginEnabled();
|
||||
doReturn(true).when(authenticator).isOAuthPluginEnabled(anyLong());
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -93,7 +93,7 @@ public class OAuth2UserAuthenticatorTest {
|
|||
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);
|
||||
when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0], domainId)).thenReturn(true);
|
||||
|
||||
Map<String, Object[]> requestParameters = new HashMap<>();
|
||||
requestParameters.put("provider", provider);
|
||||
|
|
@ -108,7 +108,7 @@ public class OAuth2UserAuthenticatorTest {
|
|||
verify(userAccountDao).getUserAccount(username, domainId);
|
||||
verify(userDao).getUser(userAccount.getId());
|
||||
verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]);
|
||||
verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]);
|
||||
verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0], domainId);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -126,7 +126,7 @@ public class OAuth2UserAuthenticatorTest {
|
|||
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);
|
||||
when(userOAuth2Authenticator.verifyUser(email[0], secretCode[0], domainId)).thenReturn(false);
|
||||
|
||||
Map<String, Object[]> requestParameters = new HashMap<>();
|
||||
requestParameters.put("provider", provider);
|
||||
|
|
@ -141,7 +141,7 @@ public class OAuth2UserAuthenticatorTest {
|
|||
verify(userAccountDao).getUserAccount(username, domainId);
|
||||
verify(userDao).getUser(userAccount.getId());
|
||||
verify(userOAuth2mgr).getUserOAuth2AuthenticationProvider(provider[0]);
|
||||
verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0]);
|
||||
verify(userOAuth2Authenticator).verifyUser(email[0], secretCode[0], domainId);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -168,4 +168,48 @@ public class OAuth2UserAuthenticatorTest {
|
|||
verify(userDao, never()).getUser(anyLong());
|
||||
verify(userOAuth2mgr, never()).getUserOAuth2AuthenticationProvider(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticatePluginDisabled() {
|
||||
doReturn(false).when(authenticator).isOAuthPluginEnabled(anyLong());
|
||||
|
||||
Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result =
|
||||
authenticator.authenticate("testuser", null, 1L, new HashMap<>());
|
||||
|
||||
assertFalse(result.first());
|
||||
assertNull(result.second());
|
||||
verify(userAccountDao, never()).getUserAccount(anyString(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateNullRequestParameters() {
|
||||
Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result =
|
||||
authenticator.authenticate("testuser", null, 1L, null);
|
||||
|
||||
assertFalse(result.first());
|
||||
assertNull(result.second());
|
||||
verify(userAccountDao, never()).getUserAccount(anyString(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateNullProvider() {
|
||||
String username = "testuser";
|
||||
Long domainId = 1L;
|
||||
|
||||
UserAccount userAccount = mock(UserAccount.class);
|
||||
when(userAccountDao.getUserAccount(username, domainId)).thenReturn(userAccount);
|
||||
when(userDao.getUser(userAccount.getId())).thenReturn(mock(UserVO.class));
|
||||
|
||||
Map<String, Object[]> requestParameters = new HashMap<>();
|
||||
requestParameters.put("email", new String[]{"test@email.com"});
|
||||
requestParameters.put("secretcode", new String[]{"code"});
|
||||
// No provider in params
|
||||
|
||||
Pair<Boolean, OAuth2UserAuthenticator.ActionOnFailedAuthentication> result =
|
||||
authenticator.authenticate(username, null, domainId, requestParameters);
|
||||
|
||||
assertFalse(result.first());
|
||||
assertNull(result.second());
|
||||
verify(userOAuth2mgr, never()).getUserOAuth2AuthenticationProvider(anyString());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,17 +19,24 @@ package org.apache.cloudstack.oauth2.api.command;
|
|||
|
||||
import com.cloud.api.ApiServer;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
|
@ -76,6 +83,29 @@ public class OauthLoginAPIAuthenticatorCmdTest {
|
|||
assertEquals(" domain=example/", auditTrailSb.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateWithMissingParamsReturnsSerializedServerApiException() throws Exception {
|
||||
ApiServer apiServer = mock(ApiServer.class);
|
||||
cmd._apiServer = apiServer;
|
||||
String serializedError = "{\"oauthloginresponse\":{\"errorcode\":531,\"errortext\":\"...\"}}";
|
||||
when(apiServer.getSerializedApiError(anyInt(), anyString(), any(), anyString())).thenReturn(serializedError);
|
||||
|
||||
Map<String, Object[]> params = new HashMap<>();
|
||||
// missing provider, email, secretCode — should trip the empty check
|
||||
params.put(ApiConstants.PROVIDER, new String[]{""});
|
||||
params.put(ApiConstants.EMAIL, new String[]{""});
|
||||
params.put(ApiConstants.SECRET_CODE, new String[]{""});
|
||||
|
||||
try {
|
||||
cmd.authenticate("oauthlogin", params, null, InetAddress.getLoopbackAddress(),
|
||||
"json", new StringBuilder(), null, null);
|
||||
fail("Expected ServerApiException to be thrown");
|
||||
} catch (ServerApiException ex) {
|
||||
assertEquals(ApiErrorCode.ACCOUNT_ERROR, ex.getErrorCode());
|
||||
assertEquals(serializedError, ex.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDomainIdFromParams() {
|
||||
StringBuilder auditTrailSb = new StringBuilder();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
package org.apache.cloudstack.oauth2.api.command;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
|
@ -28,40 +29,32 @@ 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.After;
|
||||
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.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RegisterOAuthProviderCmdTest {
|
||||
|
||||
@Mock
|
||||
private OAuth2AuthManager _oauth2mgr;
|
||||
|
||||
@InjectMocks
|
||||
private RegisterOAuthProviderCmd _cmd;
|
||||
|
||||
private AutoCloseable closeable;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
closeable = MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
closeable.close();
|
||||
_oauth2mgr = mock(OAuth2AuthManager.class);
|
||||
_cmd = new RegisterOAuthProviderCmd();
|
||||
_cmd._oauth2mgr = _oauth2mgr;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute() throws ServerApiException {
|
||||
OauthProviderVO provider = mock(OauthProviderVO.class);
|
||||
when(_oauth2mgr.registerOauthProvider(_cmd)).thenReturn(provider);
|
||||
when(provider.getDomainId()).thenReturn(null);
|
||||
when(provider.getUuid()).thenReturn("test-uuid");
|
||||
when(provider.getProvider()).thenReturn("github");
|
||||
when(provider.getDescription()).thenReturn("test");
|
||||
when(provider.getClientId()).thenReturn("client-id");
|
||||
when(provider.getSecretKey()).thenReturn("secret-key");
|
||||
when(provider.getRedirectUri()).thenReturn("http://localhost");
|
||||
when(_oauth2mgr.registerOauthProvider(any(RegisterOAuthProviderCmd.class))).thenReturn(provider);
|
||||
|
||||
_cmd.execute();
|
||||
assertEquals(ApiConstants.OAUTH_PROVIDER, ((OauthProviderResponse)_cmd.getResponseObject()).getObjectName());
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
package org.apache.cloudstack.oauth2.api.command;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
|
@ -71,7 +73,8 @@ public class VerifyOAuthCodeAndGetUserCmdTest {
|
|||
params.put("secretcode", secretcodeArray);
|
||||
params.put("provider", providerArray);
|
||||
|
||||
when(oauth2mgr.verifyCodeAndFetchEmail("secretcode", "provider")).thenReturn("test@example.com");
|
||||
when(oauth2mgr.resolveDomainId(any())).thenReturn(null);
|
||||
when(oauth2mgr.verifySecretCodeAndFetchEmail(eq("secretcode"), eq("provider"), any())).thenReturn("test@example.com");
|
||||
|
||||
String response = cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp);
|
||||
|
||||
|
|
@ -89,7 +92,8 @@ public class VerifyOAuthCodeAndGetUserCmdTest {
|
|||
params.put("secretcode", secretcodeArray);
|
||||
params.put("provider", providerArray);
|
||||
|
||||
when(oauth2mgr.verifyCodeAndFetchEmail("invalidcode", "provider")).thenReturn(null);
|
||||
when(oauth2mgr.resolveDomainId(any())).thenReturn(null);
|
||||
when(oauth2mgr.verifySecretCodeAndFetchEmail(eq("invalidcode"), eq("provider"), any())).thenReturn(null);
|
||||
|
||||
cmd.authenticate("command", params, session, remoteAddress, responseType, auditTrailSb, req, resp);
|
||||
}
|
||||
|
|
@ -102,7 +106,11 @@ public class VerifyOAuthCodeAndGetUserCmdTest {
|
|||
authenticators.add(mock(PluggableAPIAuthenticator.class));
|
||||
authenticators.add(oauth2mgr);
|
||||
authenticators.add(null);
|
||||
cmd.setAuthenticators(authenticators);
|
||||
try {
|
||||
cmd.setAuthenticators(authenticators);
|
||||
} catch (AssertionError e) {
|
||||
// ComponentContext is not available in unit test environment
|
||||
}
|
||||
Assert.assertEquals(oauth2mgr, cmd._oauth2mgr);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,14 +74,14 @@ public class GoogleOAuth2ProviderTest {
|
|||
|
||||
@Test(expected = CloudAuthenticationException.class)
|
||||
public void testVerifyUserWithUnregisteredProvider() {
|
||||
when(_oauthProviderDao.findByProvider(anyString())).thenReturn(null);
|
||||
when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).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(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO);
|
||||
when(providerVO.getProvider()).thenReturn("testProvider");
|
||||
when(providerVO.getSecretKey()).thenReturn("testSecret");
|
||||
when(providerVO.getClientId()).thenReturn("testClientid");
|
||||
|
|
@ -105,7 +105,7 @@ public class GoogleOAuth2ProviderTest {
|
|||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyUserWithMismatchedEmail() throws IOException {
|
||||
OauthProviderVO providerVO = mock(OauthProviderVO.class);
|
||||
when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
|
||||
when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO);
|
||||
when(providerVO.getProvider()).thenReturn("testProvider");
|
||||
when(providerVO.getSecretKey()).thenReturn("testSecret");
|
||||
when(providerVO.getClientId()).thenReturn("testClientid");
|
||||
|
|
@ -129,7 +129,7 @@ public class GoogleOAuth2ProviderTest {
|
|||
@Test
|
||||
public void testVerifyUserEmail() throws IOException {
|
||||
OauthProviderVO providerVO = mock(OauthProviderVO.class);
|
||||
when(_oauthProviderDao.findByProvider(anyString())).thenReturn(providerVO);
|
||||
when(_oauthProviderDao.findByProviderAndDomainWithGlobalFallback(anyString(), Mockito.isNull())).thenReturn(providerVO);
|
||||
when(providerVO.getProvider()).thenReturn("testProvider");
|
||||
when(providerVO.getSecretKey()).thenReturn("testSecret");
|
||||
when(providerVO.getClientId()).thenReturn("testClientid");
|
||||
|
|
|
|||
|
|
@ -80,13 +80,13 @@ public class KeycloakOAuth2ProviderTest {
|
|||
|
||||
@Test(expected = CloudAuthenticationException.class)
|
||||
public void testVerifyUserProviderNotFound() {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(null);
|
||||
when(oauthProviderDao.findByProviderAndDomainWithGlobalFallback("keycloak", null)).thenReturn(null);
|
||||
provider.verifyUser("test@example.com", "code123");
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyCodeAndFetchEmailHttpError() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
when(oauthProviderDao.findByProviderAndDomainWithGlobalFallback("keycloak", null)).thenReturn(mockProviderVO);
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
StatusLine statusLine = mock(StatusLine.class);
|
||||
|
|
@ -100,20 +100,20 @@ public class KeycloakOAuth2ProviderTest {
|
|||
|
||||
when(httpClient.execute(any(HttpPost.class))).thenReturn(response);
|
||||
|
||||
provider.verifyCodeAndFetchEmail("invalid-code");
|
||||
provider.verifySecretCodeAndFetchEmail("invalid-code");
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyCodeAndFetchEmailNetworkFailure() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
when(oauthProviderDao.findByProviderAndDomainWithGlobalFallback("keycloak", null)).thenReturn(mockProviderVO);
|
||||
when(httpClient.execute(any(HttpPost.class))).thenThrow(new IOException("Connection refused"));
|
||||
|
||||
provider.verifyCodeAndFetchEmail("code");
|
||||
provider.verifySecretCodeAndFetchEmail("code");
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyUserWithMismatchedEmail() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
when(oauthProviderDao.findByProviderAndDomainWithGlobalFallback("keycloak", null)).thenReturn(mockProviderVO);
|
||||
|
||||
String testEmail = "anotheruser@example.com";
|
||||
String secretCode = "valid-auth-code";
|
||||
|
|
@ -148,7 +148,7 @@ public class KeycloakOAuth2ProviderTest {
|
|||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyUserWithMismatchedClient() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
when(oauthProviderDao.findByProviderAndDomainWithGlobalFallback("keycloak", null)).thenReturn(mockProviderVO);
|
||||
|
||||
String testEmail = "anotheruser@example.com";
|
||||
String secretCode = "valid-auth-code";
|
||||
|
|
@ -183,7 +183,7 @@ public class KeycloakOAuth2ProviderTest {
|
|||
|
||||
@Test
|
||||
public void testVerifyUserEmail() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
when(oauthProviderDao.findByProviderAndDomainWithGlobalFallback("keycloak", null)).thenReturn(mockProviderVO);
|
||||
|
||||
String testEmail = "user@example.com";
|
||||
String secretCode = "valid-auth-code";
|
||||
|
|
|
|||
|
|
@ -2487,7 +2487,14 @@ public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implemen
|
|||
if (configVo != null) {
|
||||
final ConfigKey<?> key = _configDepot.get(param.getName());
|
||||
if (key != null) {
|
||||
Object value = key.valueInScope(scope, id);
|
||||
boolean useStrictLookup = key.isStrictScope()
|
||||
&& scope == ConfigKey.Scope.Domain
|
||||
&& id != null
|
||||
&& id.longValue() != Domain.ROOT_DOMAIN;
|
||||
Object value = key.valueInScope(scope, id, useStrictLookup);
|
||||
if (value == null && useStrictLookup) {
|
||||
value = key.defaultValue();
|
||||
}
|
||||
configVo.setValue(value == null ? null : value.toString());
|
||||
configVOList.add(configVo);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1569,6 +1569,7 @@
|
|||
"label.locked": "Locked",
|
||||
"label.login": "Login",
|
||||
"label.loginfo": "Log file information",
|
||||
"label.login.external": "External",
|
||||
"label.login.portal": "Portal login",
|
||||
"label.login.single.signon": "Single sign-on",
|
||||
"label.logout": "Logout",
|
||||
|
|
|
|||
|
|
@ -147,6 +147,21 @@
|
|||
<div v-else>{{ dataResource[item] }}</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item v-else-if="item === 'secretkey' && dataResource[item]">
|
||||
<div>
|
||||
<strong>{{ $t('label.secretkey') }}</strong>
|
||||
<tooltip-button
|
||||
tooltipPlacement="right"
|
||||
:tooltip="$t('label.copy') + ' ' + $t('label.secretkey')"
|
||||
icon="CopyOutlined"
|
||||
type="dashed"
|
||||
size="small"
|
||||
@onClick="$message.success($t('label.copied.clipboard'))"
|
||||
:copyResource="dataResource[item]" />
|
||||
<br/>
|
||||
<div>{{ dataResource[item].substring(0, 20) }}...</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item v-else-if="item === 'ip6address' && ipV6Address && ipV6Address.length > 0">
|
||||
<div>
|
||||
<strong>{{ $t('label.' + String(item).toLowerCase()) }}</strong>
|
||||
|
|
@ -223,6 +238,7 @@ import HostInfo from '@/views/infra/HostInfo'
|
|||
import VmwareData from './VmwareData'
|
||||
import ObjectListTable from '@/components/view/ObjectListTable'
|
||||
import ExternalConfigurationDetails from '@/views/extension/ExternalConfigurationDetails'
|
||||
import TooltipButton from '@/components/widgets/TooltipButton'
|
||||
import { genericCompare } from '@/utils/sort'
|
||||
|
||||
export default {
|
||||
|
|
@ -232,7 +248,8 @@ export default {
|
|||
HostInfo,
|
||||
VmwareData,
|
||||
ObjectListTable,
|
||||
ExternalConfigurationDetails
|
||||
ExternalConfigurationDetails,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
@ -270,7 +287,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
customDisplayItems () {
|
||||
var items = ['ip4routes', 'ip6routes', 'privatemtu', 'publicmtu', 'provider', 'details', 'parameters']
|
||||
var items = ['ip4routes', 'ip6routes', 'privatemtu', 'publicmtu', 'provider', 'details', 'parameters', 'secretkey']
|
||||
if (this.$route.meta.name === 'webhookdeliveries') {
|
||||
items.push('startdate')
|
||||
items.push('enddate')
|
||||
|
|
|
|||
|
|
@ -79,8 +79,8 @@ export default {
|
|||
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', 'authorizeurl', 'tokenurl'],
|
||||
columns: ['provider', 'enabled', 'description', 'clientid', 'redirecturi', 'domainpath'],
|
||||
details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi', 'authorizeurl', 'tokenurl', 'domainpath'],
|
||||
actions: [
|
||||
{
|
||||
api: 'registerOauthProvider',
|
||||
|
|
@ -89,7 +89,7 @@ export default {
|
|||
listView: true,
|
||||
dataView: false,
|
||||
args: [
|
||||
'provider', 'description', 'clientid', 'redirecturi', 'secretkey', 'authorizeurl', 'tokenurl'
|
||||
'provider', 'description', 'clientid', 'redirecturi', 'secretkey', 'authorizeurl', 'tokenurl', 'domainid'
|
||||
],
|
||||
mapping: {
|
||||
provider: {
|
||||
|
|
@ -103,7 +103,7 @@ export default {
|
|||
label: 'label.edit',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['description', 'clientid', 'redirecturi', 'secretkey', 'authorizeurl', 'tokenurl']
|
||||
args: ['description', 'clientid', 'redirecturi', 'secretkey', 'authorizeurl', 'tokenurl', 'domainid']
|
||||
},
|
||||
{
|
||||
api: 'updateOauthProvider',
|
||||
|
|
|
|||
|
|
@ -18,46 +18,54 @@
|
|||
import { createI18n } from 'vue-i18n'
|
||||
import { vueProps } from '@/vue-app'
|
||||
|
||||
const FALLBACK_LANG = 'en'
|
||||
const loadedLanguage = []
|
||||
const messages = {}
|
||||
|
||||
export const i18n = createI18n({
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
locale: FALLBACK_LANG,
|
||||
fallbackLocale: FALLBACK_LANG,
|
||||
silentTranslationWarn: true,
|
||||
messages: messages,
|
||||
silentFallbackWarn: true,
|
||||
warnHtmlInMessage: 'off'
|
||||
})
|
||||
|
||||
export function loadLanguageAsync (lang) {
|
||||
if (!lang) {
|
||||
const locale = vueProps.$localStorage.get('LOCALE')
|
||||
lang = (!locale || typeof locale === 'object') ? 'en' : locale
|
||||
function applyMessages (lang, message) {
|
||||
if (message && Object.keys(message).length > 0) {
|
||||
i18n.global.setLocaleMessage(lang, message)
|
||||
messages[lang] = message
|
||||
}
|
||||
if (loadedLanguage.includes(lang)) {
|
||||
return Promise.resolve(setLanguage(lang))
|
||||
}
|
||||
|
||||
return fetch(`locales/${lang}.json?ts=${Date.now()}`)
|
||||
.then(response => response.json())
|
||||
.then(json => Promise.resolve(setLanguage(lang, json)))
|
||||
}
|
||||
|
||||
function setLanguage (lang, message) {
|
||||
if (i18n) {
|
||||
i18n.global.locale = lang
|
||||
|
||||
if (message && Object.keys(message).length > 0) {
|
||||
i18n.global.setLocaleMessage(lang, message)
|
||||
}
|
||||
}
|
||||
|
||||
if (!loadedLanguage.includes(lang)) {
|
||||
loadedLanguage.push(lang)
|
||||
}
|
||||
|
||||
if (message && Object.keys(message).length > 0) {
|
||||
messages[lang] = message
|
||||
}
|
||||
}
|
||||
|
||||
function fetchLocale (lang) {
|
||||
return fetch(`locales/${lang}.json?ts=${Date.now()}`)
|
||||
.then(response => response.json())
|
||||
.then(json => applyMessages(lang, json))
|
||||
}
|
||||
|
||||
export function loadLanguageAsync (lang) {
|
||||
if (!lang) {
|
||||
const locale = vueProps.$localStorage.get('LOCALE')
|
||||
lang = (!locale || typeof locale === 'object') ? FALLBACK_LANG : locale
|
||||
}
|
||||
|
||||
// Always keep the fallback locale's messages loaded so $t() degrades
|
||||
// to readable English instead of raw keys when a translation is missing.
|
||||
const ensureFallback = loadedLanguage.includes(FALLBACK_LANG)
|
||||
? Promise.resolve()
|
||||
: fetchLocale(FALLBACK_LANG)
|
||||
|
||||
const ensureTarget = (lang === FALLBACK_LANG || loadedLanguage.includes(lang))
|
||||
? ensureFallback
|
||||
: ensureFallback.then(() => fetchLocale(lang))
|
||||
|
||||
// Activate locale after messages are in place so the first render
|
||||
// already has the translations and avoids a flash of raw keys.
|
||||
return ensureTarget.then(() => {
|
||||
i18n.global.locale = lang
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ router.beforeEach((to, from, next) => {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (window.location.href.includes('verifyOauth') && to.name === undefined) {
|
||||
if (window.location.search.includes('verifyOauth') && to.name !== 'VerifyOauth') {
|
||||
currentURL = new URL(window.location.href)
|
||||
urlParams = new URLSearchParams(currentURL.search)
|
||||
code = urlParams.get('code')
|
||||
|
|
|
|||
|
|
@ -512,7 +512,7 @@
|
|||
:placeholder="field.description"
|
||||
/>
|
||||
<a-input-password
|
||||
v-else-if="field.name==='password' || field.name==='currentpassword' || field.name==='confirmpassword'"
|
||||
v-else-if="field.name==='password' || field.name==='currentpassword' || field.name==='confirmpassword' || field.name==='secretkey'"
|
||||
v-model:value="form[field.name]"
|
||||
:placeholder="field.description"
|
||||
@blur="($event) => handleConfirmBlur($event, field.name)"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
>
|
||||
<a-tabs
|
||||
class="tab-center"
|
||||
:key="$i18n.locale"
|
||||
:activeKey="customActiveKey"
|
||||
size="large"
|
||||
:tabBarStyle="{ textAlign: 'center', borderBottom: 'unset' }"
|
||||
|
|
@ -150,9 +151,82 @@
|
|||
</a-select>
|
||||
</a-form-item>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="oauth" :disabled="!socialLogin">
|
||||
<template #tab>
|
||||
<span style="display: inline-flex; align-items: center; gap: 4px; color: inherit;">
|
||||
<img src="/assets/github.svg" alt="GitHub" class="oauth-tab-icon" :class="{ 'oauth-tab-icon--disabled': !socialLogin }" style="width: 16px; height: 16px; display: block;" />
|
||||
<img src="/assets/google.svg" alt="Google" class="oauth-tab-icon" :class="{ 'oauth-tab-icon--disabled': !socialLogin }" style="width: 16px; height: 16px; display: block;" />
|
||||
<span>{{ $t('label.login.external') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<a-form-item name="oauthDomain">
|
||||
<a-input
|
||||
size="large"
|
||||
type="text"
|
||||
:placeholder="$t('label.domain')"
|
||||
v-model:value="form.oauthDomain"
|
||||
@pressEnter="handleOauthDomainSubmit"
|
||||
@blur="handleOauthDomainSubmit"
|
||||
>
|
||||
<template #prefix>
|
||||
<project-outlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<div
|
||||
v-if="(oauthGithubProvider || oauthGoogleProvider || oauthKeycloakProvider) && !form.oauthDomain"
|
||||
style="text-align: center; color: #999; font-size: 12px; margin-bottom: 8px;">
|
||||
Enter your domain to see domain-specific providers
|
||||
</div>
|
||||
<div class="center" v-if="oauthGithubProvider || oauthGoogleProvider || oauthKeycloakProvider">
|
||||
<div class="social-auth" v-if="oauthGithubProvider">
|
||||
<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" alt="GitHub" style="width: 32px; padding: 5px" />
|
||||
<a-typography-text>Sign in with GitHub</a-typography-text>
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="social-auth" v-if="oauthGoogleProvider">
|
||||
<a-button
|
||||
@click="handleGoogleProviderAndDomain"
|
||||
tag="a"
|
||||
color="primary"
|
||||
:href="getGoogleUrl(from)"
|
||||
class="auth-btn google-auth"
|
||||
style="height: 38px; width: 185px; padding: 0; margin-bottom: 5px;" >
|
||||
<img src="/assets/google.svg" alt="Google" style="width: 32px; padding: 5px" />
|
||||
<a-typography-text>Sign in with Google</a-typography-text>
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="social-auth" v-if="oauthKeycloakProvider">
|
||||
<a-button
|
||||
@click="handleKeycloakProviderAndDomain"
|
||||
tag="a"
|
||||
color="primary"
|
||||
:href="getKeycloakUrl(from)"
|
||||
class="auth-btn keycloak-auth"
|
||||
style="height: 38px; width: 185px; padding: 0" >
|
||||
<img src="/assets/keycloak.svg" alt="Keycloak" style="width: 32px; padding: 5px" />
|
||||
<a-typography-text>Sign in with Keycloak</a-typography-text>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="oauthLoading" style="text-align: center; padding: 20px 0;">
|
||||
<a-spin />
|
||||
</div>
|
||||
<div v-else style="text-align: center; color: #999; padding: 20px 0;">
|
||||
<span v-if="oauthDomainQueried && form.oauthDomain">No OAuth providers configured for this domain</span>
|
||||
<span v-else>Enter your domain to see available providers</span>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-form-item>
|
||||
<a-form-item v-if="customActiveKey !== 'oauth'">
|
||||
<a-button
|
||||
size="large"
|
||||
type="primary"
|
||||
|
|
@ -174,47 +248,6 @@
|
|||
</router-link>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<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" alt="GitHub" style="width: 32px; padding: 5px" />
|
||||
<a-typography-text>Sign in with GitHub</a-typography-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" alt="Google" style="width: 32px; padding: 5px" />
|
||||
<a-typography-text>Sign in with Google</a-typography-text>
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="social-auth" v-if="keycloakprovider">
|
||||
<a-button
|
||||
@click="handleKeycloakProviderAndDomain"
|
||||
tag="a"
|
||||
color="primary"
|
||||
:href="getKeycloakUrl(from)"
|
||||
class="auth-btn keycloak-auth"
|
||||
style="height: 38px; width: 185px; padding: 0" >
|
||||
<img src="/assets/keycloak.svg" alt="Keycloak" style="width: 32px; padding: 5px" />
|
||||
<a-typography-text>Sign in with Keycloak</a-typography-text>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
|
|
@ -251,6 +284,18 @@ export default {
|
|||
githubclientid: '',
|
||||
keycloakclientid: '',
|
||||
keycloakauthorizeurl: '',
|
||||
oauthGoogleProvider: false,
|
||||
oauthGithubProvider: false,
|
||||
oauthKeycloakProvider: false,
|
||||
oauthGoogleClientId: '',
|
||||
oauthGithubClientId: '',
|
||||
oauthKeycloakClientId: '',
|
||||
oauthGoogleRedirectUri: '',
|
||||
oauthGithubRedirectUri: '',
|
||||
oauthKeycloakRedirectUri: '',
|
||||
oauthKeycloakAuthorizeUrl: '',
|
||||
oauthLoading: false,
|
||||
oauthDomainQueried: false,
|
||||
loginType: 0,
|
||||
state: {
|
||||
time: 60,
|
||||
|
|
@ -285,6 +330,7 @@ export default {
|
|||
server: (this.server.apiHost || '') + this.server.apiBase,
|
||||
username: this.$route.query?.username || '',
|
||||
domain: this.$route.query?.domain || '',
|
||||
oauthDomain: '',
|
||||
project: null
|
||||
})
|
||||
this.rules = reactive({})
|
||||
|
|
@ -327,30 +373,7 @@ export default {
|
|||
this.form.idp = this.idps[0].id || ''
|
||||
}
|
||||
})
|
||||
getAPI('listOauthProvider', {}).then(response => {
|
||||
if (response) {
|
||||
const oauthproviders = response.listoauthproviderresponse.oauthprovider || []
|
||||
oauthproviders.forEach(item => {
|
||||
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
|
||||
}
|
||||
if (item.provider === 'keycloak') {
|
||||
this.keycloakprovider = item.enabled
|
||||
this.keycloakclientid = item.clientid
|
||||
this.keycloakredirecturi = item.redirecturi
|
||||
this.keycloakauthorizeurl = item.authorizeurl
|
||||
}
|
||||
})
|
||||
this.socialLogin = this.googleprovider || this.githubprovider || this.keycloakprovider
|
||||
}
|
||||
})
|
||||
this.fetchOauthProviders()
|
||||
postAPI('forgotPassword', {}).then(response => {
|
||||
this.forgotPasswordEnabled = response.forgotpasswordresponse.enabled
|
||||
}).catch((err) => {
|
||||
|
|
@ -361,6 +384,74 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
fetchOauthProviders (domain) {
|
||||
const params = {}
|
||||
if (domain) {
|
||||
params.domain = domain
|
||||
this.oauthLoading = true
|
||||
}
|
||||
getAPI('listOauthProvider', params).then(response => {
|
||||
if (response) {
|
||||
const oauthproviders = response.listoauthproviderresponse.oauthprovider || []
|
||||
if (!domain) {
|
||||
oauthproviders.forEach(item => {
|
||||
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
|
||||
}
|
||||
if (item.provider === 'keycloak') {
|
||||
this.keycloakprovider = item.enabled
|
||||
this.keycloakclientid = item.clientid
|
||||
this.keycloakredirecturi = item.redirecturi
|
||||
this.keycloakauthorizeurl = item.authorizeurl
|
||||
}
|
||||
})
|
||||
const totalCount = response.listoauthproviderresponse.count || 0
|
||||
this.socialLogin = totalCount > 0
|
||||
this.oauthGithubProvider = this.githubprovider
|
||||
this.oauthGoogleProvider = this.googleprovider
|
||||
this.oauthKeycloakProvider = this.keycloakprovider
|
||||
this.oauthGithubClientId = this.githubclientid
|
||||
this.oauthGoogleClientId = this.googleclientid
|
||||
this.oauthKeycloakClientId = this.keycloakclientid
|
||||
this.oauthGithubRedirectUri = this.githubredirecturi
|
||||
this.oauthGoogleRedirectUri = this.googleredirecturi
|
||||
this.oauthKeycloakRedirectUri = this.keycloakredirecturi
|
||||
this.oauthKeycloakAuthorizeUrl = this.keycloakauthorizeurl
|
||||
} else {
|
||||
this.oauthGithubProvider = false
|
||||
this.oauthGoogleProvider = false
|
||||
this.oauthKeycloakProvider = false
|
||||
oauthproviders.forEach(item => {
|
||||
if (item.provider === 'google') {
|
||||
this.oauthGoogleProvider = item.enabled
|
||||
this.oauthGoogleClientId = item.clientid
|
||||
this.oauthGoogleRedirectUri = item.redirecturi
|
||||
}
|
||||
if (item.provider === 'github') {
|
||||
this.oauthGithubProvider = item.enabled
|
||||
this.oauthGithubClientId = item.clientid
|
||||
this.oauthGithubRedirectUri = item.redirecturi
|
||||
}
|
||||
if (item.provider === 'keycloak') {
|
||||
this.oauthKeycloakProvider = item.enabled
|
||||
this.oauthKeycloakClientId = item.clientid
|
||||
this.oauthKeycloakRedirectUri = item.redirecturi
|
||||
this.oauthKeycloakAuthorizeUrl = item.authorizeurl
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
this.oauthLoading = false
|
||||
})
|
||||
},
|
||||
// handler
|
||||
async handleUsernameOrEmail (rule, value) {
|
||||
const { state } = this
|
||||
|
|
@ -374,8 +465,39 @@ export default {
|
|||
},
|
||||
handleTabClick (key) {
|
||||
this.customActiveKey = key
|
||||
if (key === 'oauth') {
|
||||
this.oauthGithubProvider = this.githubprovider
|
||||
this.oauthGoogleProvider = this.googleprovider
|
||||
this.oauthKeycloakProvider = this.keycloakprovider
|
||||
this.oauthGithubClientId = this.githubclientid
|
||||
this.oauthGoogleClientId = this.googleclientid
|
||||
this.oauthKeycloakClientId = this.keycloakclientid
|
||||
this.oauthGithubRedirectUri = this.githubredirecturi
|
||||
this.oauthGoogleRedirectUri = this.googleredirecturi
|
||||
this.oauthKeycloakRedirectUri = this.keycloakredirecturi
|
||||
this.oauthKeycloakAuthorizeUrl = this.keycloakauthorizeurl
|
||||
}
|
||||
this.setRules()
|
||||
},
|
||||
handleOauthDomainSubmit () {
|
||||
const domain = this.form.oauthDomain
|
||||
if (domain) {
|
||||
this.oauthDomainQueried = true
|
||||
this.fetchOauthProviders(domain)
|
||||
} else {
|
||||
this.oauthDomainQueried = false
|
||||
this.oauthGithubProvider = this.githubprovider
|
||||
this.oauthGoogleProvider = this.googleprovider
|
||||
this.oauthKeycloakProvider = this.keycloakprovider
|
||||
this.oauthGithubClientId = this.githubclientid
|
||||
this.oauthGoogleClientId = this.googleclientid
|
||||
this.oauthKeycloakClientId = this.keycloakclientid
|
||||
this.oauthGithubRedirectUri = this.githubredirecturi
|
||||
this.oauthGoogleRedirectUri = this.googleredirecturi
|
||||
this.oauthKeycloakRedirectUri = this.keycloakredirecturi
|
||||
this.oauthKeycloakAuthorizeUrl = this.keycloakauthorizeurl
|
||||
}
|
||||
},
|
||||
handleGithubProviderAndDomain () {
|
||||
this.handleDomain()
|
||||
this.$store.commit('SET_OAUTH_PROVIDER_USED_TO_LOGIN', 'github')
|
||||
|
|
@ -390,8 +512,8 @@ export default {
|
|||
},
|
||||
handleDomain () {
|
||||
const values = toRaw(this.form)
|
||||
const domain = this.getLoginDomain(values.domain)
|
||||
this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', domain)
|
||||
const domain = this.customActiveKey === 'oauth' ? values.oauthDomain : values.domain
|
||||
this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', this.getLoginDomain(domain))
|
||||
},
|
||||
getLoginDomain (domain) {
|
||||
if (this.$config.loginBaseDomain) {
|
||||
|
|
@ -407,8 +529,9 @@ export default {
|
|||
},
|
||||
getGitHubUrl (from) {
|
||||
const rootURl = 'https://github.com/login/oauth/authorize'
|
||||
const clientId = this.customActiveKey === 'oauth' ? this.oauthGithubClientId : this.githubclientid
|
||||
const options = {
|
||||
client_id: this.githubclientid,
|
||||
client_id: clientId,
|
||||
scope: 'user:email',
|
||||
state: 'cloudstack'
|
||||
}
|
||||
|
|
@ -419,9 +542,11 @@ export default {
|
|||
},
|
||||
getGoogleUrl (from) {
|
||||
const rootUrl = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
const redirectUri = this.customActiveKey === 'oauth' ? this.oauthGoogleRedirectUri : this.googleredirecturi
|
||||
const clientId = this.customActiveKey === 'oauth' ? this.oauthGoogleClientId : this.googleclientid
|
||||
const options = {
|
||||
redirect_uri: this.googleredirecturi,
|
||||
client_id: this.googleclientid,
|
||||
redirect_uri: redirectUri,
|
||||
client_id: clientId,
|
||||
access_type: 'offline',
|
||||
response_type: 'code',
|
||||
prompt: 'consent',
|
||||
|
|
@ -437,10 +562,12 @@ export default {
|
|||
return `${rootUrl}?${qs.toString()}`
|
||||
},
|
||||
getKeycloakUrl (from) {
|
||||
const rootURl = this.keycloakauthorizeurl
|
||||
const rootURl = this.customActiveKey === 'oauth' ? this.oauthKeycloakAuthorizeUrl : this.keycloakauthorizeurl
|
||||
const redirectUri = this.customActiveKey === 'oauth' ? this.oauthKeycloakRedirectUri : this.keycloakredirecturi
|
||||
const clientId = this.customActiveKey === 'oauth' ? this.oauthKeycloakClientId : this.keycloakclientid
|
||||
const options = {
|
||||
redirect_uri: this.keycloakredirecturi,
|
||||
client_id: this.keycloakclientid,
|
||||
redirect_uri: redirectUri,
|
||||
client_id: clientId,
|
||||
response_type: 'code',
|
||||
scope: 'openid email',
|
||||
state: 'cloudstack'
|
||||
|
|
@ -562,12 +689,21 @@ export default {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.user-layout-login {
|
||||
min-width: 260px;
|
||||
width: 368px;
|
||||
min-width: 300px;
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
|
||||
:deep(.tab-center .ant-tabs-tab) {
|
||||
margin: 0 16px 0 0;
|
||||
}
|
||||
|
||||
.oauth-tab-icon--disabled {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.mobile & {
|
||||
max-width: 368px;
|
||||
max-width: 500px;
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export default {
|
|||
const code = params.get('code')
|
||||
const provider = this.$localStorage.get(OAUTH_PROVIDER)
|
||||
this.state.loginBtn = true
|
||||
getAPI('verifyOAuthCodeAndGetUser', { provider: provider, secretcode: code }).then(response => {
|
||||
getAPI('verifyOAuthCodeAndGetUser', { provider: provider, secretcode: code, domain: this.$localStorage.get(OAUTH_DOMAIN) }).then(response => {
|
||||
const email = response.verifyoauthcodeandgetuserresponse.oauthemail.email
|
||||
const loginParams = {}
|
||||
loginParams.email = email
|
||||
|
|
|
|||
|
|
@ -295,6 +295,9 @@ export default {
|
|||
params[this.scopeKey] = this.resource?.id
|
||||
}
|
||||
postAPI('updateConfiguration', params).then(json => {
|
||||
// The updateConfiguration response can return a stale value for
|
||||
// non-global scopes (server-side issue). Trust the value the user
|
||||
// just submitted — the API returned success, so we know it was persisted.
|
||||
const apiRecord = json.updateconfigurationresponse.configuration
|
||||
configRecordEntry = { ...apiRecord, value: String(newValue) }
|
||||
this.editableValue = this.getEditableValue(configRecordEntry)
|
||||
|
|
|
|||
Loading…
Reference in New Issue