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    /**
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}