001/*
002 *  Copyright 2020 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.plugins.mobileapp.action;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.nio.charset.StandardCharsets;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.function.Function;
026import java.util.stream.Collectors;
027
028import javax.servlet.http.HttpServletRequest;
029
030import org.apache.avalon.framework.parameters.ParameterException;
031import org.apache.avalon.framework.parameters.Parameters;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.cocoon.acting.ServiceableAction;
035import org.apache.cocoon.environment.ObjectModelHelper;
036import org.apache.cocoon.environment.Redirector;
037import org.apache.cocoon.environment.Request;
038import org.apache.cocoon.environment.SourceResolver;
039import org.apache.cocoon.environment.http.HttpEnvironment;
040import org.apache.commons.lang3.StringUtils;
041
042import org.ametys.core.authentication.AuthenticateAction;
043import org.ametys.core.authentication.CredentialProvider;
044import org.ametys.core.authentication.token.AuthenticationTokenManager;
045import org.ametys.core.cocoon.JSonReader;
046import org.ametys.core.user.CurrentUserProvider;
047import org.ametys.core.user.population.PopulationContextHelper;
048import org.ametys.core.user.population.UserPopulation;
049import org.ametys.core.user.population.UserPopulationDAO;
050import org.ametys.core.util.JSONUtils;
051import org.ametys.core.util.URIUtils;
052import org.ametys.plugins.core.impl.authentication.FormCredentialProvider;
053import org.ametys.runtime.authentication.AccessDeniedException;
054
055/**
056 * Returns the token for a user (only to be used by the mobile app on one site)
057 */
058public class GetTokenAction extends ServiceableAction
059{
060    /** Authentication Token Manager */
061    protected AuthenticationTokenManager _authenticationTokenManager;
062    
063    /** The user population DAO */
064    protected UserPopulationDAO _userPopulationDAO;
065
066    /** The helper for the associations population/context */
067    protected PopulationContextHelper _populationContextHelper;
068
069    /** The current user provider */
070    protected CurrentUserProvider _currentUserProvider;
071
072    /** JSON Utils */
073    protected JSONUtils _jsonUtils;
074    
075    @Override
076    public void service(ServiceManager smanager) throws ServiceException
077    {
078        super.service(smanager);
079        _authenticationTokenManager = (AuthenticationTokenManager) smanager.lookup(AuthenticationTokenManager.ROLE);
080
081        _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE);
082        _populationContextHelper = (PopulationContextHelper) smanager.lookup(PopulationContextHelper.ROLE);
083        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
084
085        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
086    }
087    
088    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
089    {
090        Map<String, Object> result = new HashMap<>();
091        Request request = ObjectModelHelper.getRequest(objectModel);
092        
093        
094        String body = "{}";
095        
096        if (_currentUserProvider.getUser() != null)
097        {
098            String generateToken = _authenticationTokenManager.generateToken(0, "mobileapp", "Token for the mobile app");
099            
100            result.put("code", 200);
101            result.put("token", generateToken);
102        }
103        else
104        {
105            HttpServletRequest postReq = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT);
106            try (InputStream postBody = postReq.getInputStream())
107            {
108                body = new String(postBody.readAllBytes(), StandardCharsets.UTF_8);
109            }
110            
111            Map<String, Object> jsonParams = _jsonUtils.convertJsonToMap(body);
112    
113            String login;
114            if (jsonParams.containsKey("login"))
115            {
116                login = (String) jsonParams.get("login");
117            }
118            else
119            {
120                login = request.getParameter("login");
121            }
122    
123            String password;
124            if (jsonParams.containsKey("password"))
125            {
126                password = (String) jsonParams.get("password");
127            }
128            else
129            {
130                password = request.getParameter("password");
131            }
132            boolean authenticated = false;
133            
134            if (StringUtils.isNotBlank(login))
135            {
136                authenticated = authenticate(login, password, request, resolver, parameters);
137            }
138    
139            if (authenticated)
140            {
141                String generateToken = _authenticationTokenManager.generateToken(0, "mobileapp", "Token for the mobile app");
142        
143                result.put("code", 200);
144                result.put("token", generateToken);
145                _currentUserProvider.logout();
146            }
147            else
148            {
149                result.put("code", 403);
150                throw new AccessDeniedException();
151            }
152        }
153        
154        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
155        return EMPTY_MAP;
156    }
157    
158    private boolean authenticate(String login, String password, Request request, SourceResolver resolver, Parameters parameters) throws ParameterException
159    {
160        String siteName = parameters.getParameter("site");
161        List<String> populationContexts = List.of("/sites/" + siteName, "/sites-fo/" + siteName);
162        boolean atLeastOneAvailablePopulation = false;
163        
164        for (String context : populationContexts)
165        {
166        
167            Map<UserPopulation, List<CredentialProvider>> populations = _populationContextHelper.getUserPopulationsOnContexts(List.of(context), false, false).stream()
168                    .map(_userPopulationDAO::getUserPopulation)
169                    .collect(Collectors.toMap(Function.identity(), u -> u.getCredentialProviders()));
170            
171            
172            for (Entry<UserPopulation, List<CredentialProvider>> entry : populations.entrySet())
173            {
174                UserPopulation userPopulation = entry.getKey();
175                List<CredentialProvider> providers = entry.getValue();
176                for (int i = 0; i < providers.size(); i++)
177                {
178                    CredentialProvider credentialProvider = providers.get(i);
179                    if (credentialProvider instanceof FormCredentialProvider)
180                    {
181                        try
182                        {
183                            request.setAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_AUTHENTICATED, "false");
184                            String loginParameters = "Username=" + URIUtils.encodeParameter(login);
185                            loginParameters += "&Password=" + URIUtils.encodeParameter(password);
186                            loginParameters += "&UserPopulation=" + URIUtils.encodeParameter(userPopulation.getId());
187                            loginParameters += "&CredentialProviderIndex=" + i;
188                            loginParameters += "&context=" + URIUtils.encodeParameter(context);
189                            
190                            atLeastOneAvailablePopulation = true;
191                            
192                            resolver.resolveURI("cocoon:/authenticate?" + loginParameters);
193                            if (_currentUserProvider.getUser() != null)
194                            {
195                                break;
196                            }
197                        }
198                        catch (IOException e)
199                        {
200                            getLogger().error("Impossible to test logins on population '" + userPopulation.getId() + "' using credential provider at position '" + i + "'");
201                        }
202                    }
203                }
204            }
205            
206            if (!atLeastOneAvailablePopulation)
207            {
208                getLogger().error("Error while logging-in from the mobile application to the '" + siteName + "' site. At least one population should be configured with a form credential provider.");
209            }
210        }
211        
212        return _currentUserProvider.getUser() != null;
213    }
214
215}