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