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