Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -93,6 +97,10 @@ public String getCommonNames() {
return commonNames;
}

public String getLoginBaseDomain() {
return loginBaseDomain;
}

public String getDomainIds() {
return domainIds;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -96,6 +100,10 @@ public String getJsonConfiguration() {
return jsonConfiguration;
}

public String getLoginBaseDomain() {
return loginBaseDomain;
}

public String getCommonNames() {
return commonNames;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -176,4 +181,12 @@ public void setRecursiveDomains(Boolean recursiveDomains) {
public void setRemoved(Date removed) {
this.removed = removed;
}

public String getLoginBaseDomain() {
return loginBaseDomain;
}

public void setLoginBaseDomain(String loginBaseDomain) {
this.loginBaseDomain = loginBaseDomain;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ public interface GuiThemeJoin extends InternalIdentity, Identity {
Date getCreated();

Date getRemoved();

String getLoginBaseDomain();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -138,4 +141,9 @@ public Date getCreated() {
public Date getRemoved() {
return removed;
}

@Override
public String getLoginBaseDomain() {
return loginBaseDomain;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -71,14 +74,16 @@ public GuiThemeVO() {

}

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;
this.jsonConfiguration = jsonConfiguration;
this.recursiveDomains = recursiveDomains;
this.isPublic = isPublic;
this.created = created;
this.loginBaseDomain = loginBaseDomain;
this.removed = removed;
}

Expand Down Expand Up @@ -186,4 +191,8 @@ public void setRecursiveDomains(boolean recursiveDomains) {
public String toString() {
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid", "name", "description", "isPublic", "recursiveDomains");
}

public void setLoginBaseDomain(String loginBaseDomain) {
this.loginBaseDomain = loginBaseDomain;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,6 @@ INSERT INTO cloud.role_permissions (uuid, role_id, rule, permission, sort_order)
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');
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions server/src/main/java/com/cloud/api/ApiResponseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -5719,6 +5719,7 @@ public GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin) {

guiThemeResponse.setJsonConfiguration(guiThemeJoin.getJsonConfiguration());
guiThemeResponse.setCss(guiThemeJoin.getCss());
guiThemeResponse.setLoginBaseDomain(guiThemeJoin.getLoginBaseDomain());
guiThemeResponse.setResponseName("guithemes");

return guiThemeResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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;
Expand Down Expand Up @@ -126,21 +127,18 @@ public GuiThemeJoin createGuiTheme(CreateGuiThemeCmd cmd) {
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());
Expand Down Expand Up @@ -224,14 +222,19 @@ protected Pair<List<GuiThemeJoinVO>, Integer> listGuiThemesInternal(ListGuiTheme
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);
}

validateObjectUuids(accountIds, Account.class);
validateObjectUuids(domainIds, Domain.class);
jsonConfigValidator.validateJsonConfiguration(jsonConfig);
validateLoginBaseDomain(loginBaseDomain, commonNames);
}

/**
Expand All @@ -254,6 +257,12 @@ protected void validateObjectUuids(String providedIds, Class clazz) {
}
}

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) {
Expand All @@ -267,23 +276,24 @@ public GuiThemeJoin updateGuiTheme(UpdateGuiThemeCmd cmd) {
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);

Expand Down Expand Up @@ -311,6 +321,10 @@ protected GuiThemeJoinVO persistGuiTheme(Long guiThemeId, String name, String de
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -172,6 +173,26 @@ public void validateObjectUuidsTestProvidedUuidsAreNotValidShouldThrowCloudRunti
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);
Expand Down
5 changes: 5 additions & 0 deletions ui/src/utils/guiTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 15 additions & 12 deletions ui/src/views/auth/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,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'
Expand Down Expand Up @@ -417,10 +426,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 => {
Expand Down Expand Up @@ -449,10 +455,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 => {
Expand Down
Loading