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.lang.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        if (!res.contains(getDefaultLocale())) {
367            res.add(getDefaultLocale());
368        }
369        return res;
370    }
371
372    protected void merge(LoginScreenConfig newConfig) {
373        if (newConfig.newsIframeUrl != null) {
374            setNewsIframeUrl(newConfig.newsIframeUrl);
375        }
376        if (newConfig.headerStyle != null) {
377            headerStyle = newConfig.headerStyle;
378        }
379        if (newConfig.footerStyle != null) {
380            footerStyle = newConfig.footerStyle;
381        }
382        if (newConfig.bodyBackgroundStyle != null) {
383            bodyBackgroundStyle = newConfig.bodyBackgroundStyle;
384        }
385        if (newConfig.loginBoxBackgroundStyle != null) {
386            loginBoxBackgroundStyle = newConfig.loginBoxBackgroundStyle;
387        }
388        if (newConfig.loginBoxWidth != null) {
389            loginBoxWidth = newConfig.loginBoxWidth;
390        }
391        if (newConfig.disableBackgroundSizeCover != null) {
392            disableBackgroundSizeCover = newConfig.disableBackgroundSizeCover;
393        }
394        if (newConfig.logoAlt != null) {
395            logoAlt = newConfig.logoAlt;
396        }
397        if (newConfig.logoHeight != null) {
398            logoHeight = newConfig.logoHeight;
399        }
400        if (newConfig.logoUrl != null) {
401            logoUrl = newConfig.logoUrl;
402        }
403        if (newConfig.logoWidth != null) {
404            logoWidth = newConfig.logoWidth;
405        }
406        if (newConfig.fieldAutocomplete != null) {
407            fieldAutocomplete = newConfig.fieldAutocomplete;
408        }
409        if (newConfig.videos != null) {
410            videos = newConfig.videos;
411        }
412        if (newConfig.loop != null) {
413            loop = newConfig.loop;
414        }
415        if (newConfig.removeNews) {
416            removeNews = newConfig.removeNews;
417        }
418        if (newConfig.muted != null) {
419            muted = newConfig.muted;
420        }
421        if (newConfig.loginButtonBackgroundColor != null) {
422            loginButtonBackgroundColor = newConfig.loginButtonBackgroundColor;
423        }
424        if (newConfig.backgroundImage != null) {
425            backgroundImage = newConfig.backgroundImage;
426        }
427
428        if (providers == null) {
429            providers = newConfig.providers;
430        } else if (newConfig.providers != null && newConfig.providers.size() > 0) {
431            for (LoginProviderLink link : newConfig.providers) {
432
433                int idx = providers.indexOf(link);
434                if (idx >= 0) {
435                    if (link.remove) {
436                        providers.remove(idx);
437                    } else {
438                        providers.get(idx).merge(link);
439                    }
440                } else {
441                    providers.add(link);
442                }
443            }
444        }
445
446        if (startupPages == null) {
447            startupPages = newConfig.startupPages;
448        } else if (newConfig.startupPages != null && !newConfig.startupPages.isEmpty()) {
449            for (Map.Entry<String, LoginStartupPage> startupPage : newConfig.startupPages.entrySet()) {
450                if (startupPages.containsKey(startupPage.getKey())) {
451                    startupPages.get(startupPage.getKey()).merge(startupPage.getValue());
452                } else {
453                    startupPages.put(startupPage.getKey(), startupPage.getValue());
454                }
455            }
456        }
457
458        if (newConfig.defaultLocale != null) {
459            defaultLocale = newConfig.defaultLocale;
460        }
461
462        boolean append = newConfig.isAppendSupportedLocales();
463        List<String> newLocales = newConfig.getSupportedLocales();
464        Set<String> mergedLocales = new HashSet<String>();
465        if (append && supportedLocales != null) {
466            mergedLocales.addAll(supportedLocales);
467        }
468        if (newLocales != null) {
469            mergedLocales.addAll(newLocales);
470        }
471        supportedLocales = new ArrayList<>(mergedLocales);
472    }
473
474    /**
475     * @since 7.10
476     */
477    @Override
478    public LoginScreenConfig clone() {
479        LoginScreenConfig clone = new LoginScreenConfig();
480        clone.bodyBackgroundStyle = bodyBackgroundStyle;
481        clone.disableBackgroundSizeCover = disableBackgroundSizeCover;
482        clone.fieldAutocomplete = fieldAutocomplete;
483        clone.footerStyle = footerStyle;
484        clone.headerStyle = headerStyle;
485        clone.loginBoxBackgroundStyle = loginBoxBackgroundStyle;
486        clone.loginBoxWidth = loginBoxWidth;
487        clone.loginButtonBackgroundColor = loginButtonBackgroundColor;
488        clone.logoAlt = logoAlt;
489        clone.logoHeight = logoHeight;
490        clone.logoUrl = logoUrl;
491        clone.logoWidth = logoWidth;
492        clone.loop = loop;
493        clone.muted = muted;
494        clone.newsIframeUrl = newsIframeUrl;
495        if (providers != null) {
496            clone.providers = new ArrayList<LoginProviderLink>();
497            for (LoginProviderLink l : providers) {
498                clone.providers.add(l.clone());
499            }
500        }
501        if (startupPages != null) {
502            clone.startupPages = new HashMap<String, LoginStartupPage>();
503            for (Map.Entry<String, LoginStartupPage> startupPage : startupPages.entrySet()) {
504                clone.startupPages.put(startupPage.getKey(), startupPage.getValue().clone());
505            }
506        }
507        clone.removeNews = removeNews;
508        if (videos != null) {
509            clone.videos = new ArrayList<LoginVideo>();
510            for (LoginVideo v : videos) {
511                clone.videos.add(v.clone());
512            }
513        }
514        clone.defaultLocale = defaultLocale;
515        clone.appendSupportedLocales = appendSupportedLocales;
516        if (supportedLocales != null) {
517            clone.supportedLocales = new ArrayList<>(supportedLocales);
518        }
519        return clone;
520    }
521
522}