001/* 002 * (C) Copyright 2013-2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 */ 019package org.nuxeo.ecm.platform.ui.web.auth.service; 020 021import java.io.UnsupportedEncodingException; 022import java.net.URLDecoder; 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import javax.ws.rs.core.UriBuilder; 031 032import org.apache.commons.lang3.StringUtils; 033import org.nuxeo.common.Environment; 034import org.nuxeo.common.xmap.XMap; 035import org.nuxeo.common.xmap.annotation.XNode; 036import org.nuxeo.common.xmap.annotation.XNodeList; 037import org.nuxeo.common.xmap.annotation.XNodeMap; 038import org.nuxeo.common.xmap.annotation.XObject; 039import org.nuxeo.ecm.core.api.NuxeoException; 040import org.nuxeo.runtime.api.Framework; 041 042/** 043 * {@link XMap} object to manage configuration of the login screen (login.jsp) 044 * 045 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a> 046 * @since 5.7 047 */ 048@XObject("loginScreenConfig") 049public class LoginScreenConfig { 050 051 public static final String NUXEO_NEWS_URL = "//www.nuxeo.com/login-page-embedded-1/"; 052 053 /** 054 * @since 8.4 055 */ 056 @XNodeMap(value = "startupPages/startupPage", key = "@id", type = HashMap.class, componentType = LoginStartupPage.class) 057 protected Map<String, LoginStartupPage> startupPages = new HashMap<>(); 058 059 @XNodeList(value = "loginProviders/loginProvider", type = ArrayList.class, componentType = LoginProviderLink.class) 060 protected List<LoginProviderLink> providers; 061 062 /** 063 * @since 7.10 064 */ 065 @XNodeList(value = "videos/video", type = ArrayList.class, componentType = LoginVideo.class, nullByDefault = true) 066 protected List<LoginVideo> videos; 067 068 /** 069 * @since 7.10 070 */ 071 @XNode("videos@muted") 072 protected Boolean muted; 073 074 /** 075 * @since 7.10 076 */ 077 @XNode("videos@loop") 078 protected Boolean loop; 079 080 /** 081 * @since 7.10 082 */ 083 protected String backgroundImage; 084 085 @XNode("removeNews") 086 protected Boolean removeNews = false; 087 088 protected String headerStyle; 089 090 protected String footerStyle; 091 092 protected String newsIframeUrl = NUXEO_NEWS_URL; 093 094 protected String newsIframeFullUrl = null; 095 096 protected String bodyBackgroundStyle; 097 098 protected String loginBoxBackgroundStyle; 099 100 @XNode("loginBoxWidth") 101 protected String loginBoxWidth; 102 103 protected String logoUrl; 104 105 @XNode("logoAlt") 106 protected String logoAlt; 107 108 @XNode("logoWidth") 109 protected String logoWidth; 110 111 @XNode("logoHeight") 112 protected String logoHeight; 113 114 /** 115 * @since 7.10 116 */ 117 @XNode("fieldAutocomplete") 118 protected Boolean fieldAutocomplete; 119 120 /** 121 * Boolean to disable background-cover CSS behavior on login page background, as it may not be compliant with all 122 * browsers (see NXP-12972/NXP-12978). 123 * 124 * @since 5.8 125 */ 126 @XNode("disableBackgroundSizeCover") 127 protected Boolean disableBackgroundSizeCover; 128 129 /** 130 * @since 7.10 131 */ 132 @XNode("loginButtonBackgroundColor") 133 protected String loginButtonBackgroundColor; 134 135 /** 136 * @since 8.4 137 */ 138 @XNode("defaultLocale") 139 protected String defaultLocale; 140 141 /** 142 * @since 8.4 143 */ 144 @XNode("supportedLocales@append") 145 Boolean appendSupportedLocales; 146 147 /** 148 * @since 8.4 149 */ 150 @XNodeList(value = "supportedLocales/locale", type = ArrayList.class, componentType = String.class) 151 List<String> supportedLocales; 152 153 public LoginScreenConfig() { 154 } 155 156 /** 157 * Instantiates a login screen configuration with the given login provider. 158 * 159 * @since 10.10 160 */ 161 public LoginScreenConfig(LoginProviderLink provider) { 162 providers = new ArrayList<>(); 163 providers.add(provider); 164 } 165 166 public List<LoginProviderLink> getProviders() { 167 return providers; 168 } 169 170 public void setProviders(List<LoginProviderLink> providers) { 171 this.providers = providers; 172 } 173 174 public LoginProviderLink getProvider(String name) { 175 if (getProviders() == null) { 176 return null; 177 } 178 for (LoginProviderLink provider : getProviders()) { 179 if (name.equals(provider.getName())) { 180 return provider; 181 } 182 } 183 return null; 184 } 185 186 /** 187 * @deprecated since 10.10, use {@link #LoginScreenConfig(LoginProviderLink)} instead 188 */ 189 @Deprecated 190 public void registerLoginProvider(String name, String iconUrl, String link, String label, String description, 191 LoginProviderLinkComputer computer) { 192 LoginProviderLink newProvider = new LoginProviderLink(); 193 newProvider.name = name; 194 newProvider.iconPath = iconUrl; 195 newProvider.link = link; 196 newProvider.label = label; 197 newProvider.description = description; 198 if (computer != null) { 199 newProvider.urlComputer = computer; 200 } 201 202 LoginProviderLink existingProvider = getProvider(name); 203 if (existingProvider != null) { 204 existingProvider.merge(newProvider); 205 } else { 206 if (providers == null) { 207 providers = new ArrayList<>(); 208 } 209 providers.add(newProvider); 210 } 211 } 212 213 /** 214 * @since 8.4 215 */ 216 public Map<String, LoginStartupPage> getStartupPages() { 217 return startupPages; 218 } 219 220 public String getHeaderStyle() { 221 return headerStyle; 222 } 223 224 public String getFooterStyle() { 225 return footerStyle; 226 } 227 228 public String getBodyBackgroundStyle() { 229 return bodyBackgroundStyle; 230 } 231 232 public String getLoginBoxBackgroundStyle() { 233 return loginBoxBackgroundStyle; 234 } 235 236 public String getLoginBoxWidth() { 237 return loginBoxWidth; 238 } 239 240 public String getLogoUrl() { 241 return logoUrl; 242 } 243 244 public String getLogoAlt() { 245 return logoAlt; 246 } 247 248 public String getLogoWidth() { 249 return logoWidth; 250 } 251 252 public String getLogoHeight() { 253 return logoHeight; 254 } 255 256 public List<LoginVideo> getVideos() { 257 return videos; 258 } 259 260 public Boolean getVideoMuted() { 261 return muted == null ? false : muted; 262 } 263 264 public Boolean getVideoLoop() { 265 return loop == null ? true : loop; 266 } 267 268 public boolean hasVideos() { 269 return videos != null && !videos.isEmpty(); 270 } 271 272 public boolean getDisplayNews() { 273 return !(removeNews || StringUtils.isBlank(newsIframeUrl)); 274 } 275 276 public Boolean getFieldAutocomplete() { 277 return fieldAutocomplete == null ? true : fieldAutocomplete; 278 } 279 280 @XNode("headerStyle") 281 public void setHeaderStyle(String headerStyle) { 282 this.headerStyle = Framework.expandVars(headerStyle); 283 } 284 285 @XNode("footerStyle") 286 public void setFooterStyle(String footerStyle) { 287 this.footerStyle = Framework.expandVars(footerStyle); 288 } 289 290 @XNode("bodyBackgroundStyle") 291 public void setBodyBackgroundStyle(String bodyBackgroundStyle) { 292 this.bodyBackgroundStyle = Framework.expandVars(bodyBackgroundStyle); 293 } 294 295 @XNode("backgroundImage") 296 public void setBackgroundImage(String backgroundImage) { 297 this.backgroundImage = Framework.expandVars(backgroundImage); 298 } 299 300 public String getBackgroundImage() { 301 return this.backgroundImage; 302 } 303 304 public String getLoginButtonBackgroundColor() { 305 return loginButtonBackgroundColor; 306 } 307 308 @XNode("loginBoxBackgroundStyle") 309 public void setLoginBoxBackgroundStyle(String loginBoxBackgroundStyle) { 310 this.loginBoxBackgroundStyle = Framework.expandVars(loginBoxBackgroundStyle); 311 } 312 313 @XNode("logoUrl") 314 public void setLogoUrl(String logoUrl) { 315 this.logoUrl = Framework.expandVars(logoUrl); 316 } 317 318 /** 319 * @since 7.10 320 */ 321 @XNode("newsIframeUrl") 322 public void setNewsIframeUrl(String newsIframeUrl) { 323 this.newsIframeUrl = newsIframeUrl; 324 newsIframeFullUrl = null; 325 } 326 327 public String getNewsIframeUrl() { 328 if (newsIframeFullUrl == null) { 329 UriBuilder newsIFrameBuilder = UriBuilder.fromPath(newsIframeUrl); 330 if (NUXEO_NEWS_URL.equals(newsIframeUrl)) { 331 newsIFrameBuilder.queryParam(Environment.PRODUCT_VERSION, 332 Framework.getProperty(Environment.PRODUCT_VERSION)) 333 .queryParam(Environment.DISTRIBUTION_VERSION, 334 Framework.getProperty(Environment.DISTRIBUTION_VERSION)) 335 .queryParam(Environment.DISTRIBUTION_PACKAGE, 336 Framework.getProperty(Environment.DISTRIBUTION_PACKAGE)); 337 } 338 newsIframeFullUrl = newsIFrameBuilder.build().toString(); 339 } 340 try { 341 return URLDecoder.decode(newsIframeFullUrl, "UTF-8"); 342 } catch (UnsupportedEncodingException e) { 343 throw new NuxeoException("Cannot decode login iframe URL " + newsIframeFullUrl); 344 } 345 } 346 347 /** 348 * @since 5.8 349 * @see #disableBackgroundSizeCover 350 */ 351 public Boolean getDisableBackgroundSizeCover() { 352 return disableBackgroundSizeCover; 353 } 354 355 /** 356 * @since 8.4 357 */ 358 public String getDefaultLocale() { 359 return defaultLocale; 360 } 361 362 /** 363 * @since 8.4 364 */ 365 public Boolean isAppendSupportedLocales() { 366 return appendSupportedLocales; 367 } 368 369 /** 370 * @since 8.4 371 */ 372 public List<String> getSupportedLocales() { 373 List<String> res = new ArrayList<>(); 374 if (supportedLocales != null) { 375 res.addAll(supportedLocales); 376 } 377 String defaultLocale = getDefaultLocale(); 378 if (defaultLocale != null && !res.contains(defaultLocale)) { 379 res.add(defaultLocale); 380 } 381 return res; 382 } 383 384 protected void merge(LoginScreenConfig newConfig) { 385 if (newConfig.newsIframeUrl != null) { 386 setNewsIframeUrl(newConfig.newsIframeUrl); 387 } 388 if (newConfig.headerStyle != null) { 389 headerStyle = newConfig.headerStyle; 390 } 391 if (newConfig.footerStyle != null) { 392 footerStyle = newConfig.footerStyle; 393 } 394 if (newConfig.bodyBackgroundStyle != null) { 395 bodyBackgroundStyle = newConfig.bodyBackgroundStyle; 396 } 397 if (newConfig.loginBoxBackgroundStyle != null) { 398 loginBoxBackgroundStyle = newConfig.loginBoxBackgroundStyle; 399 } 400 if (newConfig.loginBoxWidth != null) { 401 loginBoxWidth = newConfig.loginBoxWidth; 402 } 403 if (newConfig.disableBackgroundSizeCover != null) { 404 disableBackgroundSizeCover = newConfig.disableBackgroundSizeCover; 405 } 406 if (newConfig.logoAlt != null) { 407 logoAlt = newConfig.logoAlt; 408 } 409 if (newConfig.logoHeight != null) { 410 logoHeight = newConfig.logoHeight; 411 } 412 if (newConfig.logoUrl != null) { 413 logoUrl = newConfig.logoUrl; 414 } 415 if (newConfig.logoWidth != null) { 416 logoWidth = newConfig.logoWidth; 417 } 418 if (newConfig.fieldAutocomplete != null) { 419 fieldAutocomplete = newConfig.fieldAutocomplete; 420 } 421 if (newConfig.videos != null) { 422 videos = newConfig.videos; 423 } 424 if (newConfig.loop != null) { 425 loop = newConfig.loop; 426 } 427 if (newConfig.removeNews) { 428 removeNews = newConfig.removeNews; 429 } 430 if (newConfig.muted != null) { 431 muted = newConfig.muted; 432 } 433 if (newConfig.loginButtonBackgroundColor != null) { 434 loginButtonBackgroundColor = newConfig.loginButtonBackgroundColor; 435 } 436 if (newConfig.backgroundImage != null) { 437 backgroundImage = newConfig.backgroundImage; 438 } 439 440 if (providers == null) { 441 providers = newConfig.providers; 442 } else if (newConfig.providers != null && newConfig.providers.size() > 0) { 443 for (LoginProviderLink link : newConfig.providers) { 444 445 int idx = providers.indexOf(link); 446 if (idx >= 0) { 447 if (link.remove) { 448 providers.remove(idx); 449 } else { 450 providers.get(idx).merge(link); 451 } 452 } else { 453 providers.add(link); 454 } 455 } 456 } 457 458 if (startupPages == null) { 459 startupPages = newConfig.startupPages; 460 } else if (newConfig.startupPages != null && !newConfig.startupPages.isEmpty()) { 461 for (Map.Entry<String, LoginStartupPage> startupPage : newConfig.startupPages.entrySet()) { 462 if (startupPages.containsKey(startupPage.getKey())) { 463 startupPages.get(startupPage.getKey()).merge(startupPage.getValue()); 464 } else { 465 startupPages.put(startupPage.getKey(), startupPage.getValue()); 466 } 467 } 468 } 469 470 if (newConfig.defaultLocale != null) { 471 defaultLocale = newConfig.defaultLocale; 472 } 473 474 Boolean append = newConfig.isAppendSupportedLocales(); 475 List<String> newLocales = newConfig.getSupportedLocales(); 476 Set<String> mergedLocales = new HashSet<>(); 477 if (!Boolean.FALSE.equals(append) && supportedLocales != null) { 478 mergedLocales.addAll(supportedLocales); 479 } 480 if (newLocales != null) { 481 mergedLocales.addAll(newLocales); 482 } 483 supportedLocales = new ArrayList<>(mergedLocales); 484 } 485 486 /** 487 * @since 7.10 488 */ 489 @Override 490 public LoginScreenConfig clone() { 491 LoginScreenConfig clone = new LoginScreenConfig(); 492 clone.bodyBackgroundStyle = bodyBackgroundStyle; 493 clone.disableBackgroundSizeCover = disableBackgroundSizeCover; 494 clone.fieldAutocomplete = fieldAutocomplete; 495 clone.footerStyle = footerStyle; 496 clone.headerStyle = headerStyle; 497 clone.loginBoxBackgroundStyle = loginBoxBackgroundStyle; 498 clone.loginBoxWidth = loginBoxWidth; 499 clone.loginButtonBackgroundColor = loginButtonBackgroundColor; 500 clone.logoAlt = logoAlt; 501 clone.logoHeight = logoHeight; 502 clone.logoUrl = logoUrl; 503 clone.logoWidth = logoWidth; 504 clone.loop = loop; 505 clone.muted = muted; 506 clone.newsIframeUrl = newsIframeUrl; 507 if (providers != null) { 508 clone.providers = new ArrayList<>(); 509 for (LoginProviderLink l : providers) { 510 clone.providers.add(l.clone()); 511 } 512 } 513 if (startupPages != null) { 514 clone.startupPages = new HashMap<>(); 515 for (Map.Entry<String, LoginStartupPage> startupPage : startupPages.entrySet()) { 516 clone.startupPages.put(startupPage.getKey(), startupPage.getValue().clone()); 517 } 518 } 519 clone.removeNews = removeNews; 520 if (videos != null) { 521 clone.videos = new ArrayList<>(); 522 for (LoginVideo v : videos) { 523 clone.videos.add(v.clone()); 524 } 525 } 526 clone.defaultLocale = defaultLocale; 527 clone.appendSupportedLocales = appendSupportedLocales; 528 if (supportedLocales != null) { 529 clone.supportedLocales = new ArrayList<>(supportedLocales); 530 } 531 return clone; 532 } 533 534}