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:tdelprat@nuxeo.com">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    public List<LoginProviderLink> getProviders() {
160        return providers;
161    }
162
163    public void setProviders(List<LoginProviderLink> providers) {
164        this.providers = providers;
165    }
166
167    public LoginProviderLink getProvider(String name) {
168        if (getProviders() == null) {
169            return null;
170        }
171        for (LoginProviderLink provider : getProviders()) {
172            if (name.equals(provider.getName())) {
173                return provider;
174            }
175        }
176        return null;
177    }
178
179    public void registerLoginProvider(String name, String iconUrl, String link, String label, String description,
180            LoginProviderLinkComputer computer) {
181        LoginProviderLink newProvider = new LoginProviderLink();
182        newProvider.name = name;
183        newProvider.iconPath = iconUrl;
184        newProvider.link = link;
185        newProvider.label = label;
186        newProvider.description = description;
187        if (computer != null) {
188            newProvider.urlComputer = computer;
189        }
190
191        LoginProviderLink existingProvider = getProvider(name);
192        if (existingProvider != null) {
193            existingProvider.merge(newProvider);
194        } else {
195            if (providers == null) {
196                providers = new ArrayList<>();
197            }
198            providers.add(newProvider);
199        }
200    }
201
202    /**
203     * @since 8.4
204     */
205    public Map<String, LoginStartupPage> getStartupPages() {
206        return startupPages;
207    }
208
209    public String getHeaderStyle() {
210        return headerStyle;
211    }
212
213    public String getFooterStyle() {
214        return footerStyle;
215    }
216
217    public String getBodyBackgroundStyle() {
218        return bodyBackgroundStyle;
219    }
220
221    public String getLoginBoxBackgroundStyle() {
222        return loginBoxBackgroundStyle;
223    }
224
225    public String getLoginBoxWidth() {
226        return loginBoxWidth;
227    }
228
229    public String getLogoUrl() {
230        return logoUrl;
231    }
232
233    public String getLogoAlt() {
234        return logoAlt;
235    }
236
237    public String getLogoWidth() {
238        return logoWidth;
239    }
240
241    public String getLogoHeight() {
242        return logoHeight;
243    }
244
245    public List<LoginVideo> getVideos() {
246        return videos;
247    }
248
249    public Boolean getVideoMuted() {
250        return muted == null ? false : muted;
251    }
252
253    public Boolean getVideoLoop() {
254        return loop == null ? true : loop;
255    }
256
257    public boolean hasVideos() {
258        return videos != null && !videos.isEmpty();
259    }
260
261    public boolean getDisplayNews() {
262        return !(removeNews || StringUtils.isBlank(newsIframeUrl));
263    }
264
265    public Boolean getFieldAutocomplete() {
266        return fieldAutocomplete == null ? true : fieldAutocomplete;
267    }
268
269    @XNode("headerStyle")
270    public void setHeaderStyle(String headerStyle) {
271        this.headerStyle = Framework.expandVars(headerStyle);
272    }
273
274    @XNode("footerStyle")
275    public void setFooterStyle(String footerStyle) {
276        this.footerStyle = Framework.expandVars(footerStyle);
277    }
278
279    @XNode("bodyBackgroundStyle")
280    public void setBodyBackgroundStyle(String bodyBackgroundStyle) {
281        this.bodyBackgroundStyle = Framework.expandVars(bodyBackgroundStyle);
282    }
283
284    @XNode("backgroundImage")
285    public void setBackgroundImage(String backgroundImage) {
286        this.backgroundImage = Framework.expandVars(backgroundImage);
287    }
288
289    public String getBackgroundImage() {
290        return this.backgroundImage;
291    }
292
293    public String getLoginButtonBackgroundColor() {
294        return loginButtonBackgroundColor;
295    }
296
297    @XNode("loginBoxBackgroundStyle")
298    public void setLoginBoxBackgroundStyle(String loginBoxBackgroundStyle) {
299        this.loginBoxBackgroundStyle = Framework.expandVars(loginBoxBackgroundStyle);
300    }
301
302    @XNode("logoUrl")
303    public void setLogoUrl(String logoUrl) {
304        this.logoUrl = Framework.expandVars(logoUrl);
305    }
306
307    /**
308     * @since 7.10
309     */
310    @XNode("newsIframeUrl")
311    public void setNewsIframeUrl(String newsIframeUrl) {
312        this.newsIframeUrl = newsIframeUrl;
313        newsIframeFullUrl = null;
314    }
315
316    public String getNewsIframeUrl() {
317        if (newsIframeFullUrl == null) {
318            UriBuilder newsIFrameBuilder = UriBuilder.fromPath(newsIframeUrl);
319            if (NUXEO_NEWS_URL.equals(newsIframeUrl)) {
320                newsIFrameBuilder.queryParam(Environment.PRODUCT_VERSION,
321                        Framework.getProperty(Environment.PRODUCT_VERSION))
322                                 .queryParam(Environment.DISTRIBUTION_VERSION,
323                                         Framework.getProperty(Environment.DISTRIBUTION_VERSION))
324                                 .queryParam(Environment.DISTRIBUTION_PACKAGE,
325                                         Framework.getProperty(Environment.DISTRIBUTION_PACKAGE));
326            }
327            newsIframeFullUrl = newsIFrameBuilder.build().toString();
328        }
329        try {
330            return URLDecoder.decode(newsIframeFullUrl, "UTF-8");
331        } catch (UnsupportedEncodingException e) {
332            throw new NuxeoException("Cannot decode login iframe URL " + newsIframeFullUrl);
333        }
334    }
335
336    /**
337     * @since 5.8
338     * @see #disableBackgroundSizeCover
339     */
340    public Boolean getDisableBackgroundSizeCover() {
341        return disableBackgroundSizeCover;
342    }
343
344    /**
345     * @since 8.4
346     */
347    public String getDefaultLocale() {
348        return defaultLocale;
349    }
350
351    /**
352     * @since 8.4
353     */
354    public Boolean isAppendSupportedLocales() {
355        return appendSupportedLocales;
356    }
357
358    /**
359     * @since 8.4
360     */
361    public List<String> getSupportedLocales() {
362        List<String> res = new ArrayList<>();
363        if (supportedLocales != null) {
364            res.addAll(supportedLocales);
365        }
366        String defaultLocale = getDefaultLocale();
367        if (defaultLocale != null && !res.contains(defaultLocale)) {
368            res.add(defaultLocale);
369        }
370        return res;
371    }
372
373    protected void merge(LoginScreenConfig newConfig) {
374        if (newConfig.newsIframeUrl != null) {
375            setNewsIframeUrl(newConfig.newsIframeUrl);
376        }
377        if (newConfig.headerStyle != null) {
378            headerStyle = newConfig.headerStyle;
379        }
380        if (newConfig.footerStyle != null) {
381            footerStyle = newConfig.footerStyle;
382        }
383        if (newConfig.bodyBackgroundStyle != null) {
384            bodyBackgroundStyle = newConfig.bodyBackgroundStyle;
385        }
386        if (newConfig.loginBoxBackgroundStyle != null) {
387            loginBoxBackgroundStyle = newConfig.loginBoxBackgroundStyle;
388        }
389        if (newConfig.loginBoxWidth != null) {
390            loginBoxWidth = newConfig.loginBoxWidth;
391        }
392        if (newConfig.disableBackgroundSizeCover != null) {
393            disableBackgroundSizeCover = newConfig.disableBackgroundSizeCover;
394        }
395        if (newConfig.logoAlt != null) {
396            logoAlt = newConfig.logoAlt;
397        }
398        if (newConfig.logoHeight != null) {
399            logoHeight = newConfig.logoHeight;
400        }
401        if (newConfig.logoUrl != null) {
402            logoUrl = newConfig.logoUrl;
403        }
404        if (newConfig.logoWidth != null) {
405            logoWidth = newConfig.logoWidth;
406        }
407        if (newConfig.fieldAutocomplete != null) {
408            fieldAutocomplete = newConfig.fieldAutocomplete;
409        }
410        if (newConfig.videos != null) {
411            videos = newConfig.videos;
412        }
413        if (newConfig.loop != null) {
414            loop = newConfig.loop;
415        }
416        if (newConfig.removeNews) {
417            removeNews = newConfig.removeNews;
418        }
419        if (newConfig.muted != null) {
420            muted = newConfig.muted;
421        }
422        if (newConfig.loginButtonBackgroundColor != null) {
423            loginButtonBackgroundColor = newConfig.loginButtonBackgroundColor;
424        }
425        if (newConfig.backgroundImage != null) {
426            backgroundImage = newConfig.backgroundImage;
427        }
428
429        if (providers == null) {
430            providers = newConfig.providers;
431        } else if (newConfig.providers != null && newConfig.providers.size() > 0) {
432            for (LoginProviderLink link : newConfig.providers) {
433
434                int idx = providers.indexOf(link);
435                if (idx >= 0) {
436                    if (link.remove) {
437                        providers.remove(idx);
438                    } else {
439                        providers.get(idx).merge(link);
440                    }
441                } else {
442                    providers.add(link);
443                }
444            }
445        }
446
447        if (startupPages == null) {
448            startupPages = newConfig.startupPages;
449        } else if (newConfig.startupPages != null && !newConfig.startupPages.isEmpty()) {
450            for (Map.Entry<String, LoginStartupPage> startupPage : newConfig.startupPages.entrySet()) {
451                if (startupPages.containsKey(startupPage.getKey())) {
452                    startupPages.get(startupPage.getKey()).merge(startupPage.getValue());
453                } else {
454                    startupPages.put(startupPage.getKey(), startupPage.getValue());
455                }
456            }
457        }
458
459        if (newConfig.defaultLocale != null) {
460            defaultLocale = newConfig.defaultLocale;
461        }
462
463        Boolean append = newConfig.isAppendSupportedLocales();
464        List<String> newLocales = newConfig.getSupportedLocales();
465        Set<String> mergedLocales = new HashSet<String>();
466        if (!Boolean.FALSE.equals(append) && supportedLocales != null) {
467            mergedLocales.addAll(supportedLocales);
468        }
469        if (newLocales != null) {
470            mergedLocales.addAll(newLocales);
471        }
472        supportedLocales = new ArrayList<>(mergedLocales);
473    }
474
475    /**
476     * @since 7.10
477     */
478    @Override
479    public LoginScreenConfig clone() {
480        LoginScreenConfig clone = new LoginScreenConfig();
481        clone.bodyBackgroundStyle = bodyBackgroundStyle;
482        clone.disableBackgroundSizeCover = disableBackgroundSizeCover;
483        clone.fieldAutocomplete = fieldAutocomplete;
484        clone.footerStyle = footerStyle;
485        clone.headerStyle = headerStyle;
486        clone.loginBoxBackgroundStyle = loginBoxBackgroundStyle;
487        clone.loginBoxWidth = loginBoxWidth;
488        clone.loginButtonBackgroundColor = loginButtonBackgroundColor;
489        clone.logoAlt = logoAlt;
490        clone.logoHeight = logoHeight;
491        clone.logoUrl = logoUrl;
492        clone.logoWidth = logoWidth;
493        clone.loop = loop;
494        clone.muted = muted;
495        clone.newsIframeUrl = newsIframeUrl;
496        if (providers != null) {
497            clone.providers = new ArrayList<LoginProviderLink>();
498            for (LoginProviderLink l : providers) {
499                clone.providers.add(l.clone());
500            }
501        }
502        if (startupPages != null) {
503            clone.startupPages = new HashMap<String, LoginStartupPage>();
504            for (Map.Entry<String, LoginStartupPage> startupPage : startupPages.entrySet()) {
505                clone.startupPages.put(startupPage.getKey(), startupPage.getValue().clone());
506            }
507        }
508        clone.removeNews = removeNews;
509        if (videos != null) {
510            clone.videos = new ArrayList<LoginVideo>();
511            for (LoginVideo v : videos) {
512                clone.videos.add(v.clone());
513            }
514        }
515        clone.defaultLocale = defaultLocale;
516        clone.appendSupportedLocales = appendSupportedLocales;
517        if (supportedLocales != null) {
518            clone.supportedLocales = new ArrayList<>(supportedLocales);
519        }
520        return clone;
521    }
522
523}