001/*
002 *  Copyright 2016 Anyware Services
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 */
016package org.ametys.site;
017
018import java.io.ByteArrayInputStream;
019import java.io.ByteArrayOutputStream;
020import java.nio.charset.StandardCharsets;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.Enumeration;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028import java.util.Optional;
029import java.util.Set;
030import java.util.regex.Pattern;
031
032import org.apache.avalon.framework.configuration.Configuration;
033import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
034import org.apache.avalon.framework.parameters.Parameters;
035import org.apache.cocoon.environment.ObjectModelHelper;
036import org.apache.cocoon.environment.Redirector;
037import org.apache.cocoon.environment.Request;
038import org.apache.cocoon.environment.Session;
039import org.apache.cocoon.environment.http.HttpCookie;
040import org.apache.commons.lang3.StringUtils;
041import org.apache.http.NameValuePair;
042import org.apache.http.client.entity.UrlEncodedFormEntity;
043import org.apache.http.client.methods.CloseableHttpResponse;
044import org.apache.http.client.methods.HttpGet;
045import org.apache.http.client.methods.HttpPost;
046import org.apache.http.impl.client.CloseableHttpClient;
047import org.apache.http.message.BasicNameValuePair;
048
049import org.ametys.core.authentication.AuthenticateAction;
050import org.ametys.core.authentication.CredentialProvider;
051import org.ametys.core.user.UserIdentity;
052import org.ametys.plugins.core.user.UserDAO;
053import org.ametys.plugins.site.Site;
054import org.ametys.runtime.config.Config;
055import org.ametys.runtime.workspace.WorkspaceMatcher;
056
057/**
058 * The authenticate action for front side
059 */
060public class FrontAuthenticateAction extends AuthenticateAction
061{
062    /** url requires for authentication */
063    protected Collection<Pattern> _acceptedSiteUrlPatterns = Arrays.asList(new Pattern[]{Pattern.compile("^plugins/site/authenticate/[0-9]+$")});
064    
065    @Override
066    protected boolean _acceptedUrl(Request request)
067    {
068        // URL without server context and leading slash.
069        String url = (String) request.getAttribute(WorkspaceMatcher.IN_WORKSPACE_URL);
070        for (Pattern pattern : _acceptedSiteUrlPatterns)
071        {
072            if (pattern.matcher(url).matches())
073            {
074                // Anonymous request
075                request.setAttribute(REQUEST_ATTRIBUTE_GRANTED, true);
076
077                return true;
078            }
079        }
080        
081        return false;
082    }
083
084    @Override
085    protected void _setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode)
086    {
087        setUserIdentityInSession(request, userIdentity, credentialProvider, blockingMode);
088    }
089    
090    /**
091     * Save user identity in request
092     * @param request The request
093     * @param userIdentity The useridentity to save
094     * @param credentialProvider The credential provider used to connect
095     * @param blockingMode The mode used for the credential provider
096     */
097    public static void setUserIdentityInSession(Request request, UserIdentity userIdentity, CredentialProvider credentialProvider, boolean blockingMode)
098    {
099        Site site = (Site) request.getAttribute("site");
100        String siteName = site.getName();
101        
102        Session session = renewSession(request);
103        _resetConnectingStateToSession(request);
104        session.setAttribute(SESSION_USERIDENTITY + "-" + siteName, userIdentity);
105        session.setAttribute(SESSION_CREDENTIALPROVIDER + "-" + siteName, credentialProvider);
106        session.setAttribute(SESSION_CREDENTIALPROVIDER_MODE + "-" + siteName, blockingMode);
107    }
108    
109    @Override
110    protected UserIdentity _getUserIdentityFromSession(Request request)
111    {
112        UserIdentity userIdentityFromSession = getUserIdentityFromSession(request);
113        if (userIdentityFromSession != null)
114        {
115            return userIdentityFromSession;
116        }
117
118        Session session = request.getSession(false);
119        if (session != null)
120        {
121            Set<String> availableUserPopulationsIds = _getAvailableUserPopulationsIds(request, _getContexts(request, null));
122
123            // check if connected in the site application, not only on this site
124            userIdentityFromSession = super._getUserIdentityFromSession(request);
125            if (userIdentityFromSession != null && availableUserPopulationsIds.contains(userIdentityFromSession.getPopulationId()))
126            {
127                _setUserIdentityInSession(request, userIdentityFromSession, new UserDAO.ImpersonateCredentialProvider(), true);
128                return userIdentityFromSession;
129            }
130             
131            Enumeration<String> attributeNames = session.getAttributeNames();
132            while (attributeNames.hasMoreElements())
133            {
134                String attributeName = attributeNames.nextElement();
135                if (attributeName.startsWith(SESSION_USERIDENTITY + "-"))
136                {
137                    UserIdentity userIdentity = (UserIdentity) session.getAttribute(attributeName);
138                    
139                    if (availableUserPopulationsIds.contains(userIdentity.getPopulationId()))
140                    {
141                        _setUserIdentityInSession(request, userIdentity, new UserDAO.ImpersonateCredentialProvider(), true);
142                        return userIdentity;
143                    }
144                }
145            }
146        }
147
148        return null;
149    }
150    
151    @Override
152    protected Optional<String> _getWeakPasswordURI(Request request, UserIdentity userIdentity)
153    {
154        Site site = (Site) request.getAttribute("site");
155        
156        String defaultLanguage = "en";
157        Locale local = request.getLocale();
158        
159        String prefLanguage = local != null ? local.getLanguage() : defaultLanguage;
160
161        String weakPasswordUrl = site.getWeakPasswordUrl(prefLanguage);
162        if (weakPasswordUrl == null)
163        {
164            // try to get weak password from local language
165            weakPasswordUrl = site.getWeakPasswordUrl(prefLanguage);
166        }
167        
168        if (weakPasswordUrl == null)
169        {
170            // try to get weak password from default language
171            weakPasswordUrl = site.getWeakPasswordUrl(defaultLanguage);
172        }
173        
174        if (weakPasswordUrl == null)
175        {
176            for (String lang : site.getLanguages())
177            {
178                weakPasswordUrl = site.getWeakPasswordUrl(lang);
179                if (weakPasswordUrl != null)
180                {
181                    break;
182                }
183            }
184        }
185        
186        return Optional.ofNullable(weakPasswordUrl + "&userIdentity=" + UserIdentity.userIdentityToString(userIdentity));
187    }
188    
189    /**
190     * Get the user identity of the connected user from the session
191     * @param request The request
192     * @return The connected useridentity or null
193     */
194    public static UserIdentity getUserIdentityFromSession(Request request)
195    {
196        Site site = (Site) request.getAttribute("site");
197        String siteName = site.getName();
198        
199        return getUserIdentityFromSession(request, siteName);
200    }
201    
202    /**
203     * Get the user identity of the connected user from the session
204     * @param request The request
205     * @param siteName The current site name
206     * @return The connected useridentity or null
207     */
208    public static UserIdentity getUserIdentityFromSession(Request request, String siteName)
209    {
210        Session session = request.getSession(false);
211        if (session != null)
212        {
213            return (UserIdentity) session.getAttribute(SESSION_USERIDENTITY + "-" + siteName);
214        }
215        return null;
216    }
217    
218    @Override
219    protected CredentialProvider _getCredentialProviderFromSession(Request request)
220    {
221        return getCredentialProviderFromSession(request);
222    }
223    
224    /**
225     * Get the credential provider used for the current connection
226     * @param request The request
227     * @return The credential provider used or null
228     */
229    public static CredentialProvider getCredentialProviderFromSession(Request request)
230    {
231        Site site = (Site) request.getAttribute("site");
232        
233        if (site == null)
234        {
235            return null;
236        }
237        
238        String siteName = site.getName();
239        
240        return getCredentialProviderFromSession(request, siteName);
241    }
242    
243    /**
244     * Get the credential provider used for the current connection
245     * @param request The request
246     * @param siteName The current site name
247     * @return The credential provider used or null
248     */
249    public static CredentialProvider getCredentialProviderFromSession(Request request, String siteName)
250    {
251        Session session = request.getSession(false);
252        if (session != null)
253        {
254            return (CredentialProvider) session.getAttribute(SESSION_CREDENTIALPROVIDER + "-" + siteName);
255        }
256        return null;
257    }
258    
259    @Override
260    protected Boolean _getCredentialProviderModeFromSession(Request request)
261    {
262        return getCredentialProviderModeFromSession(request);
263    }
264    
265    /**
266     * Get the credential provider mode used for the current connection
267     * @param request The request
268     * @return The credential provider mode used or null
269     */
270    public static Boolean getCredentialProviderModeFromSession(Request request)
271    {
272        Site site = (Site) request.getAttribute("site");
273        String siteName = site.getName();
274        
275        return getCredentialProviderModeFromSession(request, siteName);
276    }
277    
278    /**
279     * Get the credential provider mode used for the current connection
280     * @param request The request
281     * @param siteName The current site name
282     * @return The credential provider mode used or null
283     */
284    public static Boolean getCredentialProviderModeFromSession(Request request, String siteName)
285    {
286        Session session = request.getSession(false);
287        if (session != null)
288        {
289            return (Boolean) session.getAttribute(SESSION_CREDENTIALPROVIDER_MODE + "-" + siteName);
290        }
291        return null;
292    }
293    
294    @Override
295    protected List<String> _getContexts(Request request, Parameters parameters)
296    {
297        Site site = (Site) request.getAttribute("site");
298        String siteName = site.getName();
299        return Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName);
300    }
301
302    @Override
303    protected String getLoginURL(Request request)
304    {
305        Site site = (Site) request.getAttribute("site");
306        String siteName = site.getName();
307        
308        return getLoginURLParameters(request, "cocoon://_generate/plugins/web/frontoffice-formbasedauthentication/login/login/" + siteName);
309    }
310    
311    @Override
312    protected String getLogoutURL(Request request)
313    {
314        Site site = (Site) request.getAttribute("site");
315        String siteName = site.getName();
316        return "cocoon://_generate/plugins/web/frontoffice-formbasedauthentication/login/logout/" + siteName;
317    }
318    
319    @Override
320    protected boolean _handleLogout(Redirector redirector, Map objectModel, String source, Parameters parameters) throws Exception
321    {
322        boolean logout = super._handleLogout(redirector, objectModel, source, parameters);
323        if (logout)
324        {
325            // Additionally we need to destroy potential server side session
326            Request request = ObjectModelHelper.getRequest(objectModel);
327            HttpCookie sessionId = (HttpCookie) request.getCookieMap().get(GeneratePageAction.__BACKOFFICE_JSESSION_ID);
328            if (sessionId != null)
329            {
330                try (CloseableHttpClient httpClient = BackOfficeRequestHelper.getHttpClient())
331                {
332                    String cmsURL = Config.getInstance().getValue("org.ametys.site.bo");
333                    HttpGet httpGet = new HttpGet(cmsURL + "/logout.html");
334                    httpGet.addHeader("Cookie", "JSESSIONID=" + sessionId.getValue());
335                    httpClient.execute(httpGet);
336                }
337            }
338        }
339        return logout;
340    }
341    
342    @Override
343    protected UserIdentity _validateToken(String token, String context)
344    {
345        // Get site names and URLs from the CMS
346        String cmsURL = Config.getInstance().getValue("org.ametys.site.bo");
347        
348        try (CloseableHttpClient httpClient = BackOfficeRequestHelper.getHttpClient())
349        {
350            HttpPost httpPost = new HttpPost(cmsURL + "/_validate_token.xml");
351            httpPost.addHeader("X-Ametys-FO", "true");
352            
353            List<NameValuePair> nvps = new ArrayList<>();
354            nvps.add(new BasicNameValuePair("token", token));
355            nvps.add(new BasicNameValuePair("tokenContext", context));
356            httpPost.setEntity(new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8));
357            
358            try (CloseableHttpResponse response = httpClient.execute(httpPost); ByteArrayOutputStream os = new ByteArrayOutputStream())
359            {
360                switch (response.getStatusLine().getStatusCode())
361                {
362                    case 200:
363                        break;
364                    
365                    case 403:
366                        throw new IllegalStateException("The CMS back-office refused the connection");
367                        
368                    case 500:
369                    default:
370                        throw new IllegalStateException("The CMS back-office returned an error");
371                }
372                
373                response.getEntity().writeTo(os);
374                try (ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()))
375                {
376                    Configuration conf = new DefaultConfigurationBuilder().build(is);
377                    
378                    String login = conf.getChild("login").getValue(null);
379                    String populationId = conf.getChild("populationId").getValue(null);
380                    
381                    if (StringUtils.isNoneBlank(login, populationId))
382                    {
383                        return new UserIdentity(login, populationId);
384                    }
385                    else
386                    {
387                        return null;
388                    }
389                }
390            }
391        }
392        catch (Exception e)
393        {
394            throw new RuntimeException("Unable to synchronize site data", e);
395        }
396    }
397}