mirror of https://github.com/apache/cloudstack.git
Merge 9df0c389fc into 5893ba5a8c
This commit is contained in:
commit
1da29222b0
|
|
@ -1330,8 +1330,10 @@ public class ApiConstants {
|
|||
public static final String VNF_CONFIGURE_MANAGEMENT = "vnfconfiguremanagement";
|
||||
public static final String VNF_CIDR_LIST = "vnfcidrlist";
|
||||
|
||||
public static final String AUTHORIZE_URL = "authorizeurl";
|
||||
public static final String CLIENT_ID = "clientid";
|
||||
public static final String REDIRECT_URI = "redirecturi";
|
||||
public static final String TOKEN_URL = "tokenurl";
|
||||
|
||||
public static final String IS_TAG_A_RULE = "istagarule";
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tin
|
|||
--- Disable/enable NICs
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' ');
|
||||
|
||||
--- Add URLs for OAuth provider
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider','authorize_url', 'VARCHAR(255) DEFAULT NULL COMMENT ''Authorize URL for OAuth initialization'' ');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider','token_url', 'VARCHAR(255) DEFAULT NULL COMMENT ''Token URL for OAuth finalization'' ');
|
||||
|
||||
--- Quota tariff/usage mapping
|
||||
CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@
|
|||
<artifactId>cloud-framework-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-rs-security-jose</artifactId>
|
||||
<version>${cs.cxf.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.apis</groupId>
|
||||
<artifactId>google-api-services-docs</artifactId>
|
||||
|
|
|
|||
|
|
@ -18,10 +18,14 @@
|
|||
//
|
||||
package org.apache.cloudstack.oauth2;
|
||||
|
||||
import com.cloud.user.dao.UserDao;
|
||||
import com.cloud.utils.component.Manager;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
|
@ -35,16 +39,11 @@ import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
|||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.cloud.utils.component.Manager;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthManager, Manager, Configurable {
|
||||
@Inject
|
||||
private UserDao _userDao;
|
||||
|
||||
@Inject
|
||||
protected OauthProviderDao _oauthProviderDao;
|
||||
|
|
@ -55,7 +54,7 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
|
||||
@Override
|
||||
public List<Class<?>> getAuthCommands() {
|
||||
List<Class<?>> cmdList = new ArrayList<Class<?>>();
|
||||
List<Class<?>> cmdList = new ArrayList<>();
|
||||
cmdList.add(OauthLoginAPIAuthenticatorCmd.class);
|
||||
cmdList.add(ListOAuthProvidersCmd.class);
|
||||
cmdList.add(VerifyOAuthCodeAndGetUserCmd.class);
|
||||
|
|
@ -84,7 +83,7 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
|
||||
@Override
|
||||
public List<Class<?>> getCommands() {
|
||||
List<Class<?>> cmdList = new ArrayList<Class<?>>();
|
||||
List<Class<?>> cmdList = new ArrayList<>();
|
||||
cmdList.add(RegisterOAuthProviderCmd.class);
|
||||
cmdList.add(DeleteOAuthProviderCmd.class);
|
||||
cmdList.add(UpdateOAuthProviderCmd.class);
|
||||
|
|
@ -127,9 +126,7 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
@Override
|
||||
public String verifyCodeAndFetchEmail(String code, String provider) {
|
||||
UserOAuth2Authenticator authenticator = getUserOAuth2AuthenticationProvider(provider);
|
||||
String email = authenticator.verifyCodeAndFetchEmail(code);
|
||||
|
||||
return email;
|
||||
return authenticator.verifyCodeAndFetchEmail(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -139,6 +136,8 @@ 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());
|
||||
String authorizeUrl = StringUtils.trim(cmd.getAuthorizeUrl());
|
||||
String tokenUrl = StringUtils.trim(cmd.getTokenUrl());
|
||||
|
||||
if (!isOAuthPluginEnabled()) {
|
||||
throw new CloudRuntimeException("OAuth is not enabled, please enable to register");
|
||||
|
|
@ -148,7 +147,7 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
throw new CloudRuntimeException(String.format("Provider with the name %s is already registered", provider));
|
||||
}
|
||||
|
||||
return saveOauthProvider(provider, description, clientId, secretKey, redirectUri);
|
||||
return saveOauthProvider(provider, description, clientId, secretKey, redirectUri, authorizeUrl, tokenUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -171,6 +170,8 @@ 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());
|
||||
String authorizeUrl = StringUtils.trim(cmd.getAuthorizeUrl());
|
||||
String tokenUrl = StringUtils.trim(cmd.getTokenUrl());
|
||||
Boolean enabled = cmd.getEnabled();
|
||||
|
||||
OauthProviderVO providerVO = _oauthProviderDao.findById(id);
|
||||
|
|
@ -190,6 +191,12 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
if (StringUtils.isNotEmpty(secretKey)) {
|
||||
providerVO.setSecretKey(secretKey);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(authorizeUrl)) {
|
||||
providerVO.setAuthorizeUrl(authorizeUrl);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(tokenUrl)) {
|
||||
providerVO.setTokenUrl(tokenUrl);
|
||||
}
|
||||
if (enabled != null) {
|
||||
providerVO.setEnabled(enabled);
|
||||
}
|
||||
|
|
@ -199,7 +206,7 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
return _oauthProviderDao.findById(id);
|
||||
}
|
||||
|
||||
private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri) {
|
||||
private OauthProviderVO saveOauthProvider(String provider, String description, String clientId, String secretKey, String redirectUri, String authorizeUrl, String tokenUrl) {
|
||||
final OauthProviderVO oauthProviderVO = new OauthProviderVO();
|
||||
|
||||
oauthProviderVO.setProvider(provider);
|
||||
|
|
@ -207,6 +214,8 @@ public class OAuth2AuthManagerImpl extends ManagerBase implements OAuth2AuthMana
|
|||
oauthProviderVO.setClientId(clientId);
|
||||
oauthProviderVO.setSecretKey(secretKey);
|
||||
oauthProviderVO.setRedirectUri(redirectUri);
|
||||
oauthProviderVO.setAuthorizeUrl(authorizeUrl);
|
||||
oauthProviderVO.setTokenUrl(tokenUrl);
|
||||
oauthProviderVO.setEnabled(true);
|
||||
|
||||
_oauthProviderDao.persist(oauthProviderVO);
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ import java.util.ArrayList;
|
|||
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;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
|
|
@ -40,9 +42,8 @@ import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
|||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import com.cloud.api.response.ApiResponseSerializer;
|
||||
import com.cloud.user.Account;
|
||||
|
||||
@APICommand(name = "listOauthProvider", description = "List OAuth providers registered", responseObject = OauthProviderResponse.class, entityType = {},
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
|
|
@ -108,7 +109,7 @@ public class ListOAuthProvidersCmd extends BaseListCmd implements APIAuthenticat
|
|||
List<OauthProviderResponse> responses = new ArrayList<>();
|
||||
for (OauthProviderVO result : resultList) {
|
||||
OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri());
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(), result.getAuthorizeUrl(), result.getTokenUrl());
|
||||
if (OAuth2AuthManager.OAuth2IsPluginEnabled.value() && authenticatorPluginNames.contains(result.getProvider()) && result.isEnabled()) {
|
||||
r.setEnabled(true);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,26 +14,28 @@
|
|||
// limitations under the License.
|
||||
package org.apache.cloudstack.oauth2.api.command;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.persistence.EntityExistsException;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "registerOauthProvider", responseObject = SuccessResponse.class, description = "Register the OAuth2 provider in CloudStack", since = "4.19.0")
|
||||
public class RegisterOAuthProviderCmd extends BaseCmd {
|
||||
|
||||
|
|
@ -56,6 +58,12 @@ 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.AUTHORIZE_URL, type = CommandType.STRING, description = "Authorize URL for OAuth initialization (only required for keycloack provider)")
|
||||
private String authorizeUrl;
|
||||
|
||||
@Parameter(name = ApiConstants.TOKEN_URL, type = CommandType.STRING, description = "Token URL for OAuth finalization (only required for keycloak provider)")
|
||||
private String tokenUrl;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP,
|
||||
description = "Any OAuth provider details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].clientsecret=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")
|
||||
protected Map details;
|
||||
|
|
@ -85,6 +93,14 @@ public class RegisterOAuthProviderCmd extends BaseCmd {
|
|||
return redirectUri;
|
||||
}
|
||||
|
||||
public String getAuthorizeUrl() {
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
||||
public String getTokenUrl() {
|
||||
return tokenUrl;
|
||||
}
|
||||
|
||||
public Map getDetails() {
|
||||
if (MapUtils.isEmpty(details)) {
|
||||
return null;
|
||||
|
|
@ -98,10 +114,20 @@ public class RegisterOAuthProviderCmd extends BaseCmd {
|
|||
|
||||
@Override
|
||||
public void execute() throws ServerApiException, ConcurrentOperationException, EntityExistsException {
|
||||
if (StringUtils.equals("keycloak", getProvider())) {
|
||||
if (StringUtils.isBlank(getAuthorizeUrl())) {
|
||||
throw new ServerApiException(ApiErrorCode.BAD_REQUEST, "Parameter authorizeurl is mandatory for keycloak OAuth Provider");
|
||||
}
|
||||
if (StringUtils.isBlank(getTokenUrl())) {
|
||||
throw new ServerApiException(ApiErrorCode.BAD_REQUEST, "Parameter tokenurl is mandatory for keycloak OAuth Provider");
|
||||
}
|
||||
}
|
||||
|
||||
OauthProviderVO provider = _oauth2mgr.registerOauthProvider(this);
|
||||
|
||||
OauthProviderResponse response = new OauthProviderResponse(provider.getUuid(), provider.getProvider(),
|
||||
provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri());
|
||||
provider.getDescription(), provider.getClientId(), provider.getSecretKey(), provider.getRedirectUri(),
|
||||
provider.getAuthorizeUrl(), provider.getTokenUrl());
|
||||
response.setResponseName(getCommandName());
|
||||
response.setObjectName(ApiConstants.OAUTH_PROVIDER);
|
||||
setResponseObject(response);
|
||||
|
|
|
|||
|
|
@ -16,23 +16,23 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.oauth2.api.command;
|
||||
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.cloudstack.oauth2.OAuth2AuthManager;
|
||||
import org.apache.cloudstack.oauth2.api.response.OauthProviderResponse;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
@APICommand(name = "updateOauthProvider", description = "Updates the registered OAuth provider details", responseObject = OauthProviderResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0")
|
||||
|
|
@ -57,6 +57,12 @@ public final class UpdateOAuthProviderCmd extends BaseCmd {
|
|||
@Parameter(name = ApiConstants.REDIRECT_URI, type = CommandType.STRING, description = "Redirect URI pre-registered in the specific OAuth provider")
|
||||
private String redirectUri;
|
||||
|
||||
@Parameter(name = ApiConstants.AUTHORIZE_URL, type = CommandType.STRING, description = "Authorize URL pre-registered in the specific OAuth provider")
|
||||
private String authorizeUrl;
|
||||
|
||||
@Parameter(name = ApiConstants.TOKEN_URL, type = CommandType.STRING, description = "Token URL pre-registered in the specific OAuth provider")
|
||||
private String tokenUrl;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "OAuth provider will be enabled or disabled based on this value")
|
||||
private Boolean enabled;
|
||||
|
||||
|
|
@ -87,6 +93,14 @@ public final class UpdateOAuthProviderCmd extends BaseCmd {
|
|||
return redirectUri;
|
||||
}
|
||||
|
||||
public String getAuthorizeUrl() {
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
||||
public String getTokenUrl() {
|
||||
return tokenUrl;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
|
@ -115,7 +129,8 @@ public final class UpdateOAuthProviderCmd extends BaseCmd {
|
|||
OauthProviderVO result = _oauthMgr.updateOauthProvider(this);
|
||||
if (result != null) {
|
||||
OauthProviderResponse r = new OauthProviderResponse(result.getUuid(), result.getProvider(),
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri());
|
||||
result.getDescription(), result.getClientId(), result.getSecretKey(), result.getRedirectUri(),
|
||||
result.getAuthorizeUrl(), result.getTokenUrl());
|
||||
|
||||
List<UserOAuth2Authenticator> userOAuth2AuthenticatorPlugins = _oauthMgr.listUserOAuth2AuthenticationProviders();
|
||||
List<String> authenticatorPluginNames = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.oauth2.api.response;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@EntityReference(value = OauthProviderVO.class)
|
||||
public class OauthProviderResponse extends BaseResponse {
|
||||
|
||||
|
|
@ -54,18 +55,28 @@ public class OauthProviderResponse extends BaseResponse {
|
|||
@Param(description = "Redirect URI registered in the OAuth provider")
|
||||
private String redirectUri;
|
||||
|
||||
@SerializedName(ApiConstants.AUTHORIZE_URL)
|
||||
@Param(description = "Authorize URL registered in the OAuth provider")
|
||||
private String authorizeUrl;
|
||||
|
||||
@SerializedName(ApiConstants.TOKEN_URL)
|
||||
@Param(description = "Token URL registered in the OAuth provider")
|
||||
private String tokenUrl;
|
||||
|
||||
@SerializedName(ApiConstants.ENABLED)
|
||||
@Param(description = "Whether the OAuth provider is enabled or not")
|
||||
private boolean enabled;
|
||||
|
||||
public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri) {
|
||||
public OauthProviderResponse(String id, String provider, String description, String clientId, String secretKey, String redirectUri, String authorizeUrl, String tokenUrl) {
|
||||
this.id = id;
|
||||
this.provider = provider;
|
||||
this.name = provider;
|
||||
this.description = description;
|
||||
this.clientId = clientId;
|
||||
this.secretKey = secretKey;
|
||||
this.redirectUri = redirectUri;
|
||||
this.redirectUri = redirectUri;
|
||||
this.authorizeUrl = authorizeUrl;
|
||||
this.tokenUrl = tokenUrl;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
|
@ -117,6 +128,22 @@ public class OauthProviderResponse extends BaseResponse {
|
|||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getAuthorizeUrl() {
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
||||
public void setAuthorizeUrl(String authorizeUrl) {
|
||||
this.authorizeUrl = authorizeUrl;
|
||||
}
|
||||
|
||||
public String getTokenUrl() {
|
||||
return tokenUrl;
|
||||
}
|
||||
|
||||
public void setTokenUrl(String tokenUrl) {
|
||||
this.tokenUrl = tokenUrl;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,6 @@
|
|||
//under the License.
|
||||
package org.apache.cloudstack.oauth2.github;
|
||||
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
|
@ -36,6 +25,18 @@ import java.net.URL;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class GithubOAuth2Provider extends AdapterBase implements UserOAuth2Authenticator {
|
||||
|
||||
@Inject
|
||||
|
|
|
|||
|
|
@ -16,6 +16,17 @@
|
|||
//under the License.
|
||||
package org.apache.cloudstack.oauth2.google;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.cloud.exception.CloudAuthenticationException;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
|
@ -28,15 +39,6 @@ import com.google.api.client.json.JsonFactory;
|
|||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.services.oauth2.Oauth2;
|
||||
import com.google.api.services.oauth2.model.Userinfo;
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authenticator {
|
||||
|
||||
|
|
@ -78,10 +80,10 @@ public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String secretCode) {
|
||||
OauthProviderVO githubProvider = _oauthProviderDao.findByProvider(getName());
|
||||
String clientId = githubProvider.getClientId();
|
||||
String secret = githubProvider.getSecretKey();
|
||||
String redirectURI = githubProvider.getRedirectUri();
|
||||
OauthProviderVO googleProvider = _oauthProviderDao.findByProvider(getName());
|
||||
String clientId = googleProvider.getClientId();
|
||||
String secret = googleProvider.getSecretKey();
|
||||
String redirectURI = googleProvider.getRedirectUri();
|
||||
GoogleClientSecrets clientSecrets = new GoogleClientSecrets()
|
||||
.setWeb(new GoogleClientSecrets.Details()
|
||||
.setClientId(clientId)
|
||||
|
|
@ -122,7 +124,7 @@ public class GoogleOAuth2Provider extends AdapterBase implements UserOAuth2Authe
|
|||
try {
|
||||
userinfo = oauth2.userinfo().get().execute();
|
||||
} catch (IOException e) {
|
||||
throw new CloudRuntimeException(String.format("Failed to fetch the email address with the provided secret: %s" + e.getMessage()));
|
||||
throw new CloudRuntimeException(String.format("Failed to fetch the email address with the provided secret: %s", e.getMessage()));
|
||||
}
|
||||
return userinfo.getEmail();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
package org.apache.cloudstack.oauth2.keycloak;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
||||
import org.apache.cloudstack.auth.UserOAuth2Authenticator;
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
|
||||
import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import com.cloud.exception.CloudAuthenticationException;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
public class KeycloakOAuth2Provider extends AdapterBase implements UserOAuth2Authenticator {
|
||||
|
||||
protected String idToken = null;
|
||||
|
||||
@Inject
|
||||
OauthProviderDao oauthProviderDao;
|
||||
|
||||
private CloseableHttpClient httpClient;
|
||||
|
||||
public KeycloakOAuth2Provider() {
|
||||
this(HttpClientBuilder.create().build());
|
||||
}
|
||||
|
||||
public KeycloakOAuth2Provider(CloseableHttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "keycloak";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Keycloak OAuth2 Provider Plugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyUser(String email, String secretCode) {
|
||||
if (StringUtils.isAnyEmpty(email, secretCode)) {
|
||||
throw new CloudAuthenticationException("Either email or secret code should not be null/empty");
|
||||
}
|
||||
|
||||
OauthProviderVO providerVO = oauthProviderDao.findByProvider(getName());
|
||||
if (providerVO == null) {
|
||||
throw new CloudAuthenticationException("Keycloak provider is not registered, so user cannot be verified");
|
||||
}
|
||||
|
||||
String verifiedEmail = verifyCodeAndFetchEmail(secretCode);
|
||||
if (StringUtils.isBlank(verifiedEmail) || !email.equals(verifiedEmail)) {
|
||||
throw new CloudRuntimeException("Unable to verify the email address with the provided secret");
|
||||
}
|
||||
clearIdToken();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String verifyCodeAndFetchEmail(String secretCode) {
|
||||
OauthProviderVO provider = oauthProviderDao.findByProvider(getName());
|
||||
if (provider == null) {
|
||||
throw new CloudAuthenticationException("Keycloak provider is not registered, so user cannot be verified");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(idToken)) {
|
||||
String auth = provider.getClientId() + ":" + provider.getSecretKey();
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
List<NameValuePair> params = new ArrayList<>();
|
||||
params.add(new BasicNameValuePair("grant_type", "authorization_code"));
|
||||
params.add(new BasicNameValuePair("code", secretCode));
|
||||
params.add(new BasicNameValuePair("redirect_uri", provider.getRedirectUri()));
|
||||
|
||||
HttpPost post = new HttpPost(provider.getTokenUrl());
|
||||
post.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodedAuth);
|
||||
|
||||
try {
|
||||
post.setEntity(new UrlEncodedFormEntity(params));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new CloudRuntimeException("Unable to generate URL parameters: " + e.getMessage());
|
||||
}
|
||||
|
||||
try (CloseableHttpResponse response = httpClient.execute(post)) {
|
||||
String body = EntityUtils.toString(response.getEntity());
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
throw new CloudRuntimeException("Keycloak error during token generation: " + body);
|
||||
}
|
||||
|
||||
JsonObject json = JsonParser.parseString(body).getAsJsonObject();
|
||||
String idToken = json.get("id_token").getAsString();
|
||||
validateIdToken(idToken, provider);
|
||||
|
||||
this.idToken = idToken;
|
||||
} catch (IOException e) {
|
||||
throw new CloudRuntimeException("Unable to connect to Keycloak server", e);
|
||||
}
|
||||
}
|
||||
|
||||
return obtainEmail(idToken, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserEmailAddress() throws CloudRuntimeException {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void validateIdToken(String idTokenStr, OauthProviderVO provider) {
|
||||
JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(idTokenStr);
|
||||
JwtClaims claims = jwtConsumer.getJwtToken().getClaims();
|
||||
|
||||
if (!claims.getAudiences().contains(provider.getClientId())) {
|
||||
throw new CloudAuthenticationException("Audience mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
private String obtainEmail(String idTokenStr, OauthProviderVO provider) {
|
||||
JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(idTokenStr);
|
||||
JwtClaims claims = jwtConsumer.getJwtToken().getClaims();
|
||||
|
||||
if (!claims.getAudiences().contains(provider.getClientId())) {
|
||||
throw new CloudAuthenticationException("Audience mismatch");
|
||||
}
|
||||
|
||||
return (String) claims.getClaim("email");
|
||||
}
|
||||
|
||||
protected void clearIdToken() {
|
||||
idToken = null;
|
||||
}
|
||||
|
||||
public void setHttpClient(CloseableHttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,9 +16,8 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.oauth2.vo;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
|
|
@ -26,8 +25,11 @@ import javax.persistence.GeneratedValue;
|
|||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
||||
@Entity
|
||||
@Table(name = "oauth_provider")
|
||||
|
|
@ -55,6 +57,12 @@ public class OauthProviderVO implements Identity, InternalIdentity {
|
|||
@Column(name = "redirect_uri")
|
||||
private String redirectUri;
|
||||
|
||||
@Column(name = "authorize_url")
|
||||
private String authorizeUrl;
|
||||
|
||||
@Column(name = "token_url")
|
||||
private String tokenUrl;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN)
|
||||
private Date created;
|
||||
|
||||
|
|
@ -110,6 +118,22 @@ public class OauthProviderVO implements Identity, InternalIdentity {
|
|||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public String getAuthorizeUrl() {
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
||||
public void setAuthorizeUrl(String authorizeUrl) {
|
||||
this.authorizeUrl = authorizeUrl;
|
||||
}
|
||||
|
||||
public String getTokenUrl() {
|
||||
return tokenUrl;
|
||||
}
|
||||
|
||||
public void setTokenUrl(String tokenUrl) {
|
||||
this.tokenUrl = tokenUrl;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@
|
|||
<bean id="GithubOAuth2Provider" class="org.apache.cloudstack.oauth2.github.GithubOAuth2Provider">
|
||||
<property name="name" value="github" />
|
||||
</bean>
|
||||
<bean id="KeycloakOAuth2Provider" class="org.apache.cloudstack.oauth2.keycloak.KeycloakOAuth2Provider">
|
||||
<property name="name" value="keycloak" />
|
||||
</bean>
|
||||
|
||||
<bean id="OAuth2AuthManager" class="org.apache.cloudstack.oauth2.OAuth2AuthManagerImpl">
|
||||
<property name="name" value="OAUTH2Auth" />
|
||||
|
|
@ -45,7 +48,7 @@
|
|||
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
<property name="orderConfigKey" value="user.oauth2.providers.order" />
|
||||
<property name="excludeKey" value="oauth2.plugins.exclude" />
|
||||
<property name="orderConfigDefault" value="google,github" />
|
||||
<property name="orderConfigDefault" value="google,github,keycloak" />
|
||||
</bean>
|
||||
|
||||
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,225 @@
|
|||
//Licensed to the Apache Software Foundation (ASF) under one
|
||||
//or more contributor license agreements. See the NOTICE file
|
||||
//distributed with this work for additional information
|
||||
//regarding copyright ownership. The ASF licenses this file
|
||||
//to you under the Apache License, Version 2.0 (the
|
||||
//"License"); you may not use this file except in compliance
|
||||
//the License. You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing,
|
||||
//software distributed under the License is distributed on an
|
||||
//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
//KIND, either express or implied. See the License for the
|
||||
//specific language governing permissions and limitations
|
||||
//under the License.
|
||||
package org.apache.cloudstack.oauth2.keycloak;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.apache.cloudstack.oauth2.dao.OauthProviderDao;
|
||||
import org.apache.cloudstack.oauth2.vo.OauthProviderVO;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import com.cloud.exception.CloudAuthenticationException;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class KeycloakOAuth2ProviderTest {
|
||||
|
||||
@Mock
|
||||
private OauthProviderDao oauthProviderDao;
|
||||
|
||||
@Mock
|
||||
private CloseableHttpClient httpClient;
|
||||
|
||||
private KeycloakOAuth2Provider provider;
|
||||
|
||||
private OauthProviderVO mockProviderVO;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
provider = new KeycloakOAuth2Provider(httpClient);
|
||||
provider.oauthProviderDao = oauthProviderDao;
|
||||
|
||||
mockProviderVO = new OauthProviderVO();
|
||||
mockProviderVO.setClientId("test-client");
|
||||
mockProviderVO.setSecretKey("test-secret");
|
||||
mockProviderVO.setTokenUrl("http://localhost/token");
|
||||
mockProviderVO.setRedirectUri("http://localhost/redirect");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetName() {
|
||||
assertEquals("keycloak", provider.getName());
|
||||
}
|
||||
|
||||
@Test(expected = CloudAuthenticationException.class)
|
||||
public void testVerifyUserEmptyParams() {
|
||||
provider.verifyUser("", "");
|
||||
}
|
||||
|
||||
@Test(expected = CloudAuthenticationException.class)
|
||||
public void testVerifyUserProviderNotFound() {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(null);
|
||||
provider.verifyUser("test@example.com", "code123");
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyCodeAndFetchEmailHttpError() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
StatusLine statusLine = mock(StatusLine.class);
|
||||
|
||||
when(statusLine.getStatusCode()).thenReturn(400);
|
||||
when(response.getStatusLine()).thenReturn(statusLine);
|
||||
|
||||
HttpEntity entity = mock(HttpEntity.class);
|
||||
when(entity.getContent()).thenReturn(new ByteArrayInputStream("error".getBytes()));
|
||||
when(response.getEntity()).thenReturn(entity);
|
||||
|
||||
when(httpClient.execute(any(HttpPost.class))).thenReturn(response);
|
||||
|
||||
provider.verifyCodeAndFetchEmail("invalid-code");
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyCodeAndFetchEmailNetworkFailure() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
when(httpClient.execute(any(HttpPost.class))).thenThrow(new IOException("Connection refused"));
|
||||
|
||||
provider.verifyCodeAndFetchEmail("code");
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyUserWithMismatchedEmail() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
|
||||
String testEmail = "anotheruser@example.com";
|
||||
String secretCode = "valid-auth-code";
|
||||
|
||||
String header = "{\"alg\":\"none\"}";
|
||||
String payload = "{" +
|
||||
"\"aud\":[\"test-client\"]," +
|
||||
"\"email\":\"" + testEmail + "\"," +
|
||||
"\"iss\":\"http://keycloak\"," +
|
||||
"\"sub\":\"12345\"" +
|
||||
"}";
|
||||
|
||||
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(header.getBytes());
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding().encodeToString(payload.getBytes());
|
||||
String fakeJwt = encodedHeader + "." + encodedPayload + ".not-checked-signature";
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
StatusLine statusLine = mock(StatusLine.class);
|
||||
HttpEntity entity = mock(HttpEntity.class);
|
||||
|
||||
when(statusLine.getStatusCode()).thenReturn(200);
|
||||
when(response.getStatusLine()).thenReturn(statusLine);
|
||||
|
||||
String jsonResponseBody = "{\"id_token\":\"" + fakeJwt + "\", \"access_token\":\"acc-123\"}";
|
||||
when(entity.getContent()).thenReturn(new ByteArrayInputStream(jsonResponseBody.getBytes(StandardCharsets.UTF_8)));
|
||||
when(response.getEntity()).thenReturn(entity);
|
||||
|
||||
when(httpClient.execute(any(HttpPost.class))).thenReturn(response);
|
||||
|
||||
provider.verifyUser("user@example.com", secretCode);
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testVerifyUserWithMismatchedClient() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
|
||||
String testEmail = "anotheruser@example.com";
|
||||
String secretCode = "valid-auth-code";
|
||||
|
||||
String header = "{\"alg\":\"none\"}";
|
||||
String payload = "{" +
|
||||
"\"aud\":[\"anothertest-client\"]," +
|
||||
"\"email\":\"" + testEmail + "\"," +
|
||||
"\"iss\":\"http://keycloak\"," +
|
||||
"\"sub\":\"12345\"" +
|
||||
"}";
|
||||
|
||||
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(header.getBytes());
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding().encodeToString(payload.getBytes());
|
||||
String fakeJwt = encodedHeader + "." + encodedPayload + ".not-checked-signature";
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
StatusLine statusLine = mock(StatusLine.class);
|
||||
HttpEntity entity = mock(HttpEntity.class);
|
||||
|
||||
when(statusLine.getStatusCode()).thenReturn(200);
|
||||
when(response.getStatusLine()).thenReturn(statusLine);
|
||||
|
||||
String jsonResponseBody = "{\"id_token\":\"" + fakeJwt + "\", \"access_token\":\"acc-123\"}";
|
||||
when(entity.getContent()).thenReturn(new ByteArrayInputStream(jsonResponseBody.getBytes(StandardCharsets.UTF_8)));
|
||||
when(response.getEntity()).thenReturn(entity);
|
||||
|
||||
when(httpClient.execute(any(HttpPost.class))).thenReturn(response);
|
||||
|
||||
provider.verifyUser(testEmail, secretCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyUserEmail() throws IOException {
|
||||
when(oauthProviderDao.findByProvider("keycloak")).thenReturn(mockProviderVO);
|
||||
|
||||
String testEmail = "user@example.com";
|
||||
String secretCode = "valid-auth-code";
|
||||
|
||||
String header = "{\"alg\":\"none\"}";
|
||||
String payload = "{" +
|
||||
"\"aud\":[\"test-client\"]," +
|
||||
"\"email\":\"" + testEmail + "\"," +
|
||||
"\"iss\":\"http://keycloak\"," +
|
||||
"\"sub\":\"12345\"" +
|
||||
"}";
|
||||
|
||||
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(header.getBytes());
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding().encodeToString(payload.getBytes());
|
||||
String fakeJwt = encodedHeader + "." + encodedPayload + ".not-checked-signature";
|
||||
|
||||
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
|
||||
StatusLine statusLine = mock(StatusLine.class);
|
||||
HttpEntity entity = mock(HttpEntity.class);
|
||||
|
||||
when(statusLine.getStatusCode()).thenReturn(200);
|
||||
when(response.getStatusLine()).thenReturn(statusLine);
|
||||
|
||||
String jsonResponseBody = "{\"id_token\":\"" + fakeJwt + "\", \"access_token\":\"acc-123\"}";
|
||||
when(entity.getContent()).thenReturn(new ByteArrayInputStream(jsonResponseBody.getBytes(StandardCharsets.UTF_8)));
|
||||
when(response.getEntity()).thenReturn(entity);
|
||||
|
||||
when(httpClient.execute(any(HttpPost.class))).thenReturn(response);
|
||||
|
||||
boolean result = provider.verifyUser(testEmail, secretCode);
|
||||
|
||||
assertTrue("User successfully verified", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDescription() {
|
||||
assertEquals("Keycloak OAuth2 Provider Plugin", provider.getDescription());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 512 512"><style>.st9{fill:#d0d0d0}.st11{fill:#d9d9d9}.st13{fill:#d8d8d8}.st14{fill:#e2e2e2}.st16{fill:#dedede}.st21{fill:#00b8e3}.st22{fill:#33c6e9}.st23{fill:#008aaa}</style><g id="g2460" transform="translate(.714 .07)"><path id="path1588" d="M432.9 149.2c-1.4 0-2.7-.7-3.4-2L370.1 44.1c-.7-1.2-2-2-3.5-2H124.2c-1.4 0-2.7.7-3.4 2L58.9 150.9l23.9 34.9c-.7 1.2-6.2 24-5.5 25.2L58.9 360.9l61.9 106.9c.7 1.2 2 2 3.4 2h242.4c1.4 0 2.7-.7 3.5-2l59.4-103.2c.7-1.2 2-2 3.4-2h73.8c2.4 0 4.4-2 4.4-4.4V153.6c0-2.4-2-4.4-4.4-4.4z" style="fill:#4d4d4d"/><path id="path1594" d="M72.7 245.3 6.4 269.4l-6.6-11.3c-.7-1.2-.7-2.7 0-3.9l30-52z" style="fill:#e1e1e1"/><path id="polygon1794" d="M511.3 258.3V309l-43.7-44.5z" style="fill:#c8c8c8"/><path id="path1798" d="m467.5 264.5 43.7 44.5v49.6c0 2.4-2 4.4-4.4 4.4H456z" style="fill:#c2c2c2"/><path id="polygon1802" d="M467.5 264.5 456 362.9h-61.2l-18.5-44.7z" style="fill:#c7c7c7"/><path id="polygon1804" d="M511.3 211.2v47l-43.7 6.2z" style="fill:#cecece"/><path id="path1808" d="M511.3 153.6v57.6l-43.7 53.2-33.1-115.3h72.2c2.4-.1 4.5 1.8 4.6 4.3z" style="fill:#d3d3d3"/><path id="polygon1812" d="M394.8 362.9h-32.3l-8.4-12 22.1-32.7z" style="fill:#c6c6c6"/><path id="polygon1814" d="m467.5 264.5-121.1-51.2 63.7-64.1h24.4z" style="fill:#d5d5d5"/><path id="path1816" d="m346.5 213.3 29.8 105 91.2-53.8z" class="st9"/><path id="polygon1818" d="m353.8 362.9.4-12 8.4 12z" style="fill:#bfbfbf"/><path id="polygon1820" d="m410.1 149.2-63.7 64.1-11.4-57.4 24.6-6.8h50.5z" class="st11"/><path id="path1822" d="m346.5 213.3-147 33.9 154.7 103.7z" style="fill:#d4d4d4"/><path id="path1824" d="m346.5 213.3 7.7 137.6 22.1-32.7z" class="st9"/><path id="path1826" d="m335 155.9-135.5 91.2 147-33.9z" class="st11"/><path id="polygon1828" d="m199.5 247.2-63.7 115.7H99.6L72.7 245.3z" class="st13"/><path id="path1830" d="m134.3 149.2-61.5 96.1L57.3 155l2.2-3.8c.7-1.2 2-1.9 3.4-1.9z" class="st14"/><path id="path1832" d="M99.6 362.9H62.7c-1.4 0-2.8-.8-3.5-2L6.4 269.4l66.4-24.1z" class="st13"/><path id="polygon1834" d="M29.9 202.1 57.1 155l15.7 90.3z" style="fill:#e4e4e4"/><path id="polygon1836" d="m335 155.9-40.8-6.8H159.4l40.1 98z" class="st16"/><path id="polygon1838" d="m199.5 247.2-40.1-98h-25.1l-61.5 96.1z" class="st16"/><path id="polygon1840" d="M324.7 362.9h29.1l.4-12z" style="fill:#c5c5c5"/><path id="polygon1842" d="M266.7 362.9h58l29.5-12-154.7-103.7 27.9 115.7z" class="st9"/><path id="polygon1844" d="m227.4 362.9-27.9-115.7-63.7 115.7z" style="fill:#d1d1d1"/><path id="polygon1856" d="m335.4 149.2-.4 6.8 24.6-6.8z" style="fill:#ddd"/><path id="polygon1858" d="m335 155.9-3.8-6.8h-37z" style="fill:#e3e3e3"/><path id="polygon1860" d="m335 155.9.4-6.8h-4.2z" class="st14"/><path id="path1862" d="m223.9 151-59.7 103.4c-.3.5-.4 1.1-.4 1.7h-41.7l82-142q.75.45 1.2 1.2l18.6 32.3c.5 1.1.5 2.4 0 3.4" class="st21"/><path id="path1864" d="M223.8 364.9 205.3 397q-.45.75-1.2 1.2l-82-142.2h41.7c0 .6.1 1.1.4 1.6l59.6 103.2c.8 1.2.9 2.9 0 4.1" class="st22"/><path id="path1866" d="m204 114.2-82 141.9-20.6 35.6-19.6-34c-.3-.5-.4-1-.4-1.6s.1-1.2.4-1.7l19.9-34.4 60.4-104.5c.6-1.1 1.8-1.8 3-1.8h37.2c.6 0 1.2.2 1.7.5" class="st23"/><path id="path1868" d="M204 398.2c-.5.3-1.1.5-1.8.5h-37.1c-1.3 0-2.4-.7-3-1.8l-55.2-95.6-5.5-9.5 20.6-35.6z" class="st21"/><path id="path1870" d="m368.9 256.1-82 142q-.75-.45-1.2-1.2L267 364.7c-.5-1-.5-2.3 0-3.3L326.7 258c.3-.5.5-1.2.5-1.8z" class="st23"/><path id="path1872" d="M409.4 256.1c0 .6-.2 1.3-.5 1.8l-80.3 139.3c-.6 1-1.8 1.7-3 1.6h-37c-.6 0-1.2-.2-1.8-.5L368.9 256l20.6-35.6 19.5 33.8c.3.7.4 1.3.4 1.9" class="st21"/><path id="path1874" d="M368.9 256.1h-41.7c0-.6-.2-1.2-.5-1.8L267 151.2c-.6-1.1-.6-2.5 0-3.6l18.6-32.2q.45-.75 1.2-1.2z" class="st21"/><path id="path1876" d="m389.4 220.5-20.6 35.6-82-142c.6-.3 1.2-.5 1.8-.5h37.1c1.2 0 2.3.6 3 1.6z" class="st22"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -438,6 +438,7 @@
|
|||
"label.attaching": "Attaching",
|
||||
"label.authentication.method": "Authentication Method",
|
||||
"label.authentication.sshkey": "System SSH Key",
|
||||
"label.authorizeurl": "Authorize URL",
|
||||
"label.use.existing.vcenter.credentials.from.zone": "Use existing vCenter credentials from the Zone",
|
||||
"label.autoscale": "AutoScale",
|
||||
"label.autoscalevmgroupname": "AutoScaling Group",
|
||||
|
|
@ -2584,6 +2585,7 @@
|
|||
"label.to": "to",
|
||||
"label.token": "Token",
|
||||
"label.token.for.dashboard.login": "Token for dashboard login can be retrieved using following command",
|
||||
"label.tokenurl": "Token URL",
|
||||
"label.tools": "Tools",
|
||||
"label.total": "Total",
|
||||
"label.total.network": "Total Networks",
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default {
|
|||
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'],
|
||||
details: ['provider', 'description', 'enabled', 'clientid', 'secretkey', 'redirecturi', 'authorizeurl', 'tokenurl'],
|
||||
actions: [
|
||||
{
|
||||
api: 'registerOauthProvider',
|
||||
|
|
@ -89,11 +89,11 @@ export default {
|
|||
listView: true,
|
||||
dataView: false,
|
||||
args: [
|
||||
'provider', 'description', 'clientid', 'redirecturi', 'secretkey'
|
||||
'provider', 'description', 'clientid', 'redirecturi', 'secretkey', 'authorizeurl', 'tokenurl'
|
||||
],
|
||||
mapping: {
|
||||
provider: {
|
||||
options: ['google', 'github']
|
||||
options: ['google', 'github', 'keycloak']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -103,7 +103,7 @@ export default {
|
|||
label: 'label.edit',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['description', 'clientid', 'redirecturi', 'secretkey']
|
||||
args: ['description', 'clientid', 'redirecturi', 'secretkey', 'authorizeurl', 'tokenurl']
|
||||
},
|
||||
{
|
||||
api: 'updateOauthProvider',
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@
|
|||
:href="getGitHubUrl(from)"
|
||||
class="auth-btn github-auth"
|
||||
style="height: 38px; width: 185px; padding: 0; margin-bottom: 5px;" >
|
||||
<img src="/assets/github.svg" style="width: 32px; padding: 5px" />
|
||||
<img src="/assets/github.svg" alt="Google" style="width: 32px; padding: 5px" />
|
||||
<a-typography-text>Sign in with Github</a-typography-text>
|
||||
</a-button>
|
||||
</div>
|
||||
|
|
@ -198,10 +198,22 @@
|
|||
:href="getGoogleUrl(from)"
|
||||
class="auth-btn google-auth"
|
||||
style="height: 38px; width: 185px; padding: 0" >
|
||||
<img src="/assets/google.svg" style="width: 32px; padding: 5px" />
|
||||
<img src="/assets/google.svg" alt="Github" 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>
|
||||
|
|
@ -231,10 +243,14 @@ export default {
|
|||
socialLogin: false,
|
||||
googleprovider: false,
|
||||
githubprovider: false,
|
||||
keycloakprovider: false,
|
||||
googleredirecturi: '',
|
||||
githubredirecturi: '',
|
||||
keycloakredirecturi: '',
|
||||
googleclientid: '',
|
||||
githubclientid: '',
|
||||
keycloakclientid: '',
|
||||
keycloakauthorizeurl: '',
|
||||
loginType: 0,
|
||||
state: {
|
||||
time: 60,
|
||||
|
|
@ -325,8 +341,14 @@ export default {
|
|||
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.socialLogin = this.googleprovider || this.githubprovider || this.keycloakprovider
|
||||
}
|
||||
})
|
||||
postAPI('forgotPassword', {}).then(response => {
|
||||
|
|
@ -362,6 +384,10 @@ export default {
|
|||
this.handleDomain()
|
||||
this.$store.commit('SET_OAUTH_PROVIDER_USED_TO_LOGIN', 'google')
|
||||
},
|
||||
handleKeycloakProviderAndDomain () {
|
||||
this.handleDomain()
|
||||
this.$store.commit('SET_OAUTH_PROVIDER_USED_TO_LOGIN', 'keycloak')
|
||||
},
|
||||
handleDomain () {
|
||||
const values = toRaw(this.form)
|
||||
if (!values.domain) {
|
||||
|
|
@ -401,6 +427,20 @@ export default {
|
|||
|
||||
return `${rootUrl}?${qs.toString()}`
|
||||
},
|
||||
getKeycloakUrl (from) {
|
||||
const rootURl = this.keycloakauthorizeurl
|
||||
const options = {
|
||||
redirect_uri: this.keycloakredirecturi,
|
||||
client_id: this.keycloakclientid,
|
||||
response_type: 'code',
|
||||
scope: 'openid email',
|
||||
state: 'cloudstack'
|
||||
}
|
||||
|
||||
const qs = new URLSearchParams(options)
|
||||
|
||||
return `${rootURl}?${qs.toString()}`
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (this.state.loginBtn) return
|
||||
|
|
|
|||
Loading…
Reference in New Issue