mirror of https://github.com/apache/cloudstack.git
Custom login base domain using GUI whitelabel themes (#13412)
This commit is contained in:
parent
5c4bc486d2
commit
6f30b0d583
|
|
@ -1408,6 +1408,7 @@ public class ApiConstants {
|
|||
public static final String CSS = "css";
|
||||
|
||||
public static final String JSON_CONFIGURATION = "jsonconfiguration";
|
||||
public static final String LOGIN_BASE_DOMAIN = "loginbasedomain";
|
||||
|
||||
public static final String COMMON_NAMES = "commonnames";
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ public class CreateGuiThemeCmd extends BaseCmd {
|
|||
"wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com")
|
||||
private String commonNames;
|
||||
|
||||
@Parameter(name = ApiConstants.LOGIN_BASE_DOMAIN, type = CommandType.STRING, length = 65535, description = "The ACS domain to be used as base " +
|
||||
"for the login when accessing the GUI through the common name defined in the theme. If a common name is not defined, this parameter is ignored on the GUI.")
|
||||
private String loginBaseDomain;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_IDS, type = CommandType.STRING, length = 65535, description = "A set of domain UUIDs (also known as ID for " +
|
||||
"the end-user) separated by comma that can retrieve the theme.")
|
||||
private String domainIds;
|
||||
|
|
@ -93,6 +97,10 @@ public class CreateGuiThemeCmd extends BaseCmd {
|
|||
return commonNames;
|
||||
}
|
||||
|
||||
public String getLoginBaseDomain() {
|
||||
return loginBaseDomain;
|
||||
}
|
||||
|
||||
public String getDomainIds() {
|
||||
return domainIds;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ public class UpdateGuiThemeCmd extends BaseCmd {
|
|||
"wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com")
|
||||
private String commonNames;
|
||||
|
||||
@Parameter(name = ApiConstants.LOGIN_BASE_DOMAIN, type = CommandType.STRING, length = 65535, description = "The ACS domain to be used as base for " +
|
||||
"the login when accessing the GUI through the common name defined in the theme. If a common name is not defined, this parameter is ignored on the GUI.")
|
||||
private String loginBaseDomain;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN_IDS, type = CommandType.STRING, length = 65535, description = "A set of domain UUIDs (also known as ID for " +
|
||||
"the end-user) separated by comma that can retrieve the theme.")
|
||||
private String domainIds;
|
||||
|
|
@ -96,6 +100,10 @@ public class UpdateGuiThemeCmd extends BaseCmd {
|
|||
return jsonConfiguration;
|
||||
}
|
||||
|
||||
public String getLoginBaseDomain() {
|
||||
return loginBaseDomain;
|
||||
}
|
||||
|
||||
public String getCommonNames() {
|
||||
return commonNames;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ public class GuiThemeResponse extends BaseResponse {
|
|||
@Param(description = "A set of Common Names (CN) (fixed or wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com")
|
||||
private String commonNames;
|
||||
|
||||
@SerializedName(ApiConstants.LOGIN_BASE_DOMAIN)
|
||||
@Param(description = "The ACS domain to be used as base for the login when accessing the GUI through the common name defined in the theme. If a " +
|
||||
"common name is not defined, this parameter is ignored on the GUI.")
|
||||
private String loginBaseDomain;
|
||||
|
||||
@SerializedName(ApiConstants.DOMAIN_IDS)
|
||||
@Param(description = "A set of domain UUIDs (also known as ID for the end-user) separated by comma that can retrieve the theme.")
|
||||
private String domainIds;
|
||||
|
|
@ -176,4 +181,12 @@ public class GuiThemeResponse extends BaseResponse {
|
|||
public void setRemoved(Date removed) {
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public String getLoginBaseDomain() {
|
||||
return loginBaseDomain;
|
||||
}
|
||||
|
||||
public void setLoginBaseDomain(String loginBaseDomain) {
|
||||
this.loginBaseDomain = loginBaseDomain;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,4 +44,6 @@ public interface GuiThemeJoin extends InternalIdentity, Identity {
|
|||
Date getCreated();
|
||||
|
||||
Date getRemoved();
|
||||
|
||||
String getLoginBaseDomain();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ public class GuiThemeJoinVO implements GuiThemeJoin {
|
|||
@Column(name = "is_public")
|
||||
private boolean isPublic;
|
||||
|
||||
@Column(name = "login_base_domain")
|
||||
private String loginBaseDomain;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(value = TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
|
@ -138,4 +141,9 @@ public class GuiThemeJoinVO implements GuiThemeJoin {
|
|||
public Date getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLoginBaseDomain() {
|
||||
return loginBaseDomain;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ public class GuiThemeVO implements GuiTheme {
|
|||
@Column(name = "recursive_domains")
|
||||
private boolean recursiveDomains = false;
|
||||
|
||||
@Column(name = "login_base_domain", length = 65535)
|
||||
private String loginBaseDomain;
|
||||
|
||||
@Column(name = GenericDao.CREATED_COLUMN, nullable = false)
|
||||
@Temporal(value = TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
|
@ -71,7 +74,8 @@ public class GuiThemeVO implements GuiTheme {
|
|||
|
||||
}
|
||||
|
||||
public GuiThemeVO(String name, String description, String css, String jsonConfiguration, boolean recursiveDomains, boolean isPublic, Date created, Date removed) {
|
||||
public GuiThemeVO(String name, String description, String css, String jsonConfiguration, boolean recursiveDomains,
|
||||
boolean isPublic, Date created, String loginBaseDomain, Date removed) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.css = css;
|
||||
|
|
@ -79,6 +83,7 @@ public class GuiThemeVO implements GuiTheme {
|
|||
this.recursiveDomains = recursiveDomains;
|
||||
this.isPublic = isPublic;
|
||||
this.created = created;
|
||||
this.loginBaseDomain = loginBaseDomain;
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
|
|
@ -186,4 +191,8 @@ public class GuiThemeVO implements GuiTheme {
|
|||
public String toString() {
|
||||
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid", "name", "description", "isPublic", "recursiveDomains");
|
||||
}
|
||||
|
||||
public void setLoginBaseDomain(String loginBaseDomain) {
|
||||
this.loginBaseDomain = loginBaseDomain;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -451,6 +451,9 @@ SELECT uuid(), role_id, 'quotaResourceStatement', permission, sort_order
|
|||
FROM cloud.role_permissions rp
|
||||
WHERE rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaResourceStatement');
|
||||
|
||||
--- Gui theme login base domain
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.gui_themes', 'login_base_domain', 'TEXT DEFAULT NULL');
|
||||
|
||||
-- Add description for secondary IP addresses
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nic_secondary_ips', 'description', 'VARCHAR(2048) DEFAULT NULL');
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ SELECT
|
|||
`cloud`.`gui_themes`.`description` AS `description`,
|
||||
`cloud`.`gui_themes`.`css` AS `css`,
|
||||
`cloud`.`gui_themes`.`json_configuration` AS `json_configuration`,
|
||||
`cloud`.`gui_themes`.`login_base_domain` AS `login_base_domain`,
|
||||
(SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'commonName' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) common_names,
|
||||
(SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'domain' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) domains,
|
||||
(SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'account' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) accounts,
|
||||
|
|
|
|||
|
|
@ -5721,6 +5721,7 @@ protected Map<String, ResourceIcon> getResourceIconsUsingOsCategory(List<Templat
|
|||
|
||||
guiThemeResponse.setJsonConfiguration(guiThemeJoin.getJsonConfiguration());
|
||||
guiThemeResponse.setCss(guiThemeJoin.getCss());
|
||||
guiThemeResponse.setLoginBaseDomain(guiThemeJoin.getLoginBaseDomain());
|
||||
guiThemeResponse.setResponseName("guithemes");
|
||||
|
||||
return guiThemeResponse;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.apache.cloudstack.gui.theme.dao.GuiThemeDao;
|
|||
import org.apache.cloudstack.gui.theme.dao.GuiThemeDetailsDao;
|
||||
import org.apache.cloudstack.gui.theme.dao.GuiThemeJoinDao;
|
||||
import org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigValidator;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
|
@ -126,21 +127,18 @@ public class GuiThemeServiceImpl implements GuiThemeService {
|
|||
String providedAccountIds = cmd.getAccountIds();
|
||||
boolean isPublic = cmd.getPublic();
|
||||
Boolean recursiveDomains = cmd.getRecursiveDomains();
|
||||
String baseDomainName = cmd.getLoginBaseDomain();
|
||||
|
||||
CallContext.current().setEventDetails(String.format("Name: %s, AccountIDs: %s, DomainIDs: %s, RecursiveDomains: %s, CommonNames: %s", name, providedAccountIds,
|
||||
providedDomainIds, recursiveDomains, commonNames));
|
||||
|
||||
if (StringUtils.isAllBlank(css, jsonConfiguration)) {
|
||||
throw new CloudRuntimeException("Either the `css` or `jsonConfiguration` parameter must be informed.");
|
||||
}
|
||||
|
||||
validateParameters(jsonConfiguration, providedDomainIds, providedAccountIds, commonNames, null);
|
||||
validateParameters(css, jsonConfiguration, providedDomainIds, providedAccountIds, commonNames, baseDomainName, null);
|
||||
|
||||
if (shouldSetGuiThemeToPrivate(providedDomainIds, providedAccountIds)) {
|
||||
isPublic = false;
|
||||
}
|
||||
|
||||
GuiThemeVO guiThemeVO = new GuiThemeVO(name, description, css, jsonConfiguration, recursiveDomains, isPublic, new Date(), null);
|
||||
GuiThemeVO guiThemeVO = new GuiThemeVO(name, description, css, jsonConfiguration, recursiveDomains, isPublic, new Date(), cmd.getLoginBaseDomain(), null);
|
||||
guiThemeDao.persist(guiThemeVO);
|
||||
persistGuiThemeDetails(guiThemeVO.getId(), commonNames, providedDomainIds, providedAccountIds);
|
||||
return guiThemeJoinDao.findById(guiThemeVO.getId());
|
||||
|
|
@ -224,7 +222,11 @@ public class GuiThemeServiceImpl implements GuiThemeService {
|
|||
return guiThemeJoinDao.listGuiThemes(id, name, commonName, domainUuid, accountUuid, listAll, showRemoved, showPublic);
|
||||
}
|
||||
|
||||
protected void validateParameters(String jsonConfig, String domainIds, String accountIds, String commonNames, Long idOfThemeToBeUpdated) {
|
||||
protected void validateParameters(String css, String jsonConfig, String domainIds, String accountIds, String commonNames, String loginBaseDomain, Long idOfThemeToBeUpdated) {
|
||||
if (StringUtils.isAllBlank(css, jsonConfig, loginBaseDomain)) {
|
||||
throw new CloudRuntimeException("At least one of the `css`, `jsonconfiguration`, or `loginbasedomain` parameters must be informed.");
|
||||
}
|
||||
|
||||
if (isConsideredDefaultTheme(commonNames, domainIds, accountIds)) {
|
||||
checkIfDefaultThemeIsAllowed(commonNames, domainIds, accountIds, idOfThemeToBeUpdated);
|
||||
}
|
||||
|
|
@ -232,6 +234,7 @@ public class GuiThemeServiceImpl implements GuiThemeService {
|
|||
validateObjectUuids(accountIds, Account.class);
|
||||
validateObjectUuids(domainIds, Domain.class);
|
||||
jsonConfigValidator.validateJsonConfiguration(jsonConfig);
|
||||
validateLoginBaseDomain(loginBaseDomain, commonNames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -254,6 +257,12 @@ public class GuiThemeServiceImpl implements GuiThemeService {
|
|||
}
|
||||
}
|
||||
|
||||
protected void validateLoginBaseDomain(String loginBaseDomain, String commonNames) {
|
||||
if (loginBaseDomain != null && StringUtils.isBlank(commonNames)) {
|
||||
throw new CloudRuntimeException("Parameter `loginBaseDomain` must be provided with `commonNames`.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_GUI_THEME_UPDATE, eventDescription = "Updating GUI theme")
|
||||
public GuiThemeJoin updateGuiTheme(UpdateGuiThemeCmd cmd) {
|
||||
|
|
@ -267,23 +276,24 @@ public class GuiThemeServiceImpl implements GuiThemeService {
|
|||
String commonNames = cmd.getCommonNames() == null ? guiThemeJoinVO.getCommonNames() : cmd.getCommonNames();
|
||||
String providedDomainIds = cmd.getDomainIds() == null ? guiThemeJoinVO.getDomains() : cmd.getDomainIds();
|
||||
String providedAccountIds = cmd.getAccountIds() == null ? guiThemeJoinVO.getAccounts() : cmd.getAccountIds();
|
||||
String baseDomainName = ObjectUtils.defaultIfNull(cmd.getLoginBaseDomain(), guiThemeJoinVO.getLoginBaseDomain());
|
||||
Boolean isPublic = cmd.getIsPublic();
|
||||
Boolean recursiveDomains = cmd.getRecursiveDomains();
|
||||
|
||||
CallContext.current().setEventDetails(String.format("ID: %s, Name: %s, AccountIDs: %s, DomainIDs: %s, RecursiveDomains: %s, CommonNames: %s", guiThemeId, name,
|
||||
providedAccountIds, providedDomainIds, recursiveDomains, commonNames));
|
||||
|
||||
validateParameters(jsonConfiguration, providedDomainIds, providedAccountIds, commonNames, guiThemeId);
|
||||
validateParameters(css, jsonConfiguration, providedDomainIds, providedAccountIds, commonNames, baseDomainName, guiThemeId);
|
||||
|
||||
if (shouldSetGuiThemeToPrivate(providedDomainIds, providedAccountIds)) {
|
||||
isPublic = false;
|
||||
}
|
||||
|
||||
return persistGuiTheme(guiThemeId, name, description, css, jsonConfiguration, commonNames, providedDomainIds, providedAccountIds, isPublic, recursiveDomains);
|
||||
return persistGuiTheme(guiThemeId, name, description, css, jsonConfiguration, commonNames, providedDomainIds, providedAccountIds, isPublic, recursiveDomains, baseDomainName);
|
||||
}
|
||||
|
||||
protected GuiThemeJoinVO persistGuiTheme(Long guiThemeId, String name, String description, String css, String jsonConfiguration, String commonNames, String providedDomainIds,
|
||||
String providedAccountIds, Boolean isPublic, Boolean recursiveDomains){
|
||||
String providedAccountIds, Boolean isPublic, Boolean recursiveDomains, String loginBaseDomain){
|
||||
return Transaction.execute((TransactionCallback<GuiThemeJoinVO>) status -> {
|
||||
GuiThemeVO guiThemeVO = guiThemeDao.findById(guiThemeId);
|
||||
|
||||
|
|
@ -311,6 +321,10 @@ public class GuiThemeServiceImpl implements GuiThemeService {
|
|||
guiThemeVO.setRecursiveDomains(recursiveDomains);
|
||||
}
|
||||
|
||||
if (loginBaseDomain != null) {
|
||||
guiThemeVO.setLoginBaseDomain(loginBaseDomain);
|
||||
}
|
||||
|
||||
logger.trace("Persisting GUI theme [{}] with CSS [{}] and JSON configuration [{}].", guiThemeVO, guiThemeVO.getCss(), guiThemeVO.getJsonConfiguration());
|
||||
|
||||
guiThemeDao.persist(guiThemeVO);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ public class GuiThemeServiceImplTest {
|
|||
|
||||
private static final String ACCOUNT_IDS = "4,5,6";
|
||||
|
||||
private static final String LOGIN_BASE_DOMAIN = "acmedomain";
|
||||
private static final String BLANK_STRING = "";
|
||||
|
||||
@Test
|
||||
|
|
@ -172,6 +173,26 @@ public class GuiThemeServiceImplTest {
|
|||
guiThemeServiceSpy.validateObjectUuids(ACCOUNT_IDS, Account.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateLoginBaseDomainTestBaseDomainIsNullCommonNamesIsNullShouldNotThrowCloudRuntimeException() {
|
||||
guiThemeServiceSpy.validateLoginBaseDomain(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateLoginBaseDomainTestBaseDomainIsNullCommonNamesIsNotNullShouldNotThrowCloudRuntimeException() {
|
||||
guiThemeServiceSpy.validateLoginBaseDomain(null, COMMON_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateLoginBaseDomainTestBaseDomainIsNotNullCommonNamesIsNotNullShouldNotThrowCloudRuntimeException() {
|
||||
guiThemeServiceSpy.validateLoginBaseDomain(LOGIN_BASE_DOMAIN, COMMON_NAME);
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void validateLoginBaseDomainTestBaseDomainIsNotNullCommonNamesIsNullShouldNotThrowCloudRuntimeException() {
|
||||
guiThemeServiceSpy.validateLoginBaseDomain(LOGIN_BASE_DOMAIN, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfDefaultThemeIsAllowedTestThemeIsNotConsideredDefault() {
|
||||
Mockito.when(guiThemeServiceSpy.isConsideredDefaultTheme(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(false);
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@ async function applyDynamicCustomization (response) {
|
|||
jsonConfig = JSON.parse(response?.jsonconfiguration)
|
||||
}
|
||||
|
||||
vueProps.$config.loginBaseDomain = ''
|
||||
if (response?.loginbasedomain) {
|
||||
vueProps.$config.loginBaseDomain = response.loginbasedomain
|
||||
}
|
||||
|
||||
// Sets custom GUI fields only if is not nullish.
|
||||
vueProps.$config.appTitle = jsonConfig?.appTitle ?? vueProps.$config.appTitle
|
||||
vueProps.$config.footer = jsonConfig?.footer ?? vueProps.$config.footer
|
||||
|
|
|
|||
|
|
@ -390,11 +390,20 @@ export default {
|
|||
},
|
||||
handleDomain () {
|
||||
const values = toRaw(this.form)
|
||||
if (!values.domain) {
|
||||
this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', '/')
|
||||
} else {
|
||||
this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', values.domain)
|
||||
const domain = this.getLoginDomain(values.domain)
|
||||
this.$store.commit('SET_DOMAIN_USED_TO_LOGIN', domain)
|
||||
},
|
||||
getLoginDomain (domain) {
|
||||
if (this.$config.loginBaseDomain) {
|
||||
if (domain) {
|
||||
return this.$config.loginBaseDomain + '/' + domain
|
||||
}
|
||||
return this.$config.loginBaseDomain
|
||||
}
|
||||
if (domain) {
|
||||
return domain
|
||||
}
|
||||
return '/'
|
||||
},
|
||||
getGitHubUrl (from) {
|
||||
const rootURl = 'https://github.com/login/oauth/authorize'
|
||||
|
|
@ -457,10 +466,7 @@ export default {
|
|||
delete loginParams.username
|
||||
loginParams[!this.state.loginType ? 'email' : 'username'] = values.username
|
||||
loginParams.password = values.password
|
||||
loginParams.domain = values.domain
|
||||
if (!loginParams.domain) {
|
||||
loginParams.domain = '/'
|
||||
}
|
||||
loginParams.domain = this.getLoginDomain(values.domain)
|
||||
this.Login(loginParams)
|
||||
.then((res) => this.loginSuccess(res))
|
||||
.catch(err => {
|
||||
|
|
@ -489,10 +495,7 @@ export default {
|
|||
loginParams.email = this.email
|
||||
loginParams.provider = provider
|
||||
loginParams.secretcode = this.secretcode
|
||||
loginParams.domain = values.domain
|
||||
if (!loginParams.domain) {
|
||||
loginParams.domain = '/'
|
||||
}
|
||||
loginParams.domain = this.getLoginDomain(values.domain)
|
||||
this.OauthLogin(loginParams)
|
||||
.then((res) => this.loginSuccess(res))
|
||||
.catch(err => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue