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.InputStream; 019import java.nio.charset.StandardCharsets; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import javax.servlet.http.HttpServletRequest; 026 027import org.apache.avalon.framework.parameters.ParameterException; 028import org.apache.avalon.framework.parameters.Parameters; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.thread.ThreadSafe; 032import org.apache.cocoon.acting.ServiceableAction; 033import org.apache.cocoon.environment.ObjectModelHelper; 034import org.apache.cocoon.environment.Redirector; 035import org.apache.cocoon.environment.Request; 036import org.apache.cocoon.environment.SourceResolver; 037import org.apache.cocoon.environment.http.HttpEnvironment; 038 039import org.ametys.core.authentication.CredentialProvider; 040import org.ametys.core.authentication.token.AuthenticationTokenManager; 041import org.ametys.core.cocoon.JSonReader; 042import org.ametys.core.user.CurrentUserProvider; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.core.user.population.PopulationContextHelper; 045import org.ametys.core.user.population.UserPopulation; 046import org.ametys.core.user.population.UserPopulationDAO; 047import org.ametys.core.util.JSONUtils; 048import org.ametys.runtime.authentication.AccessDeniedException; 049 050/** 051 * Returns the token for a user (only to be used by the mobile app on one site) 052 */ 053public abstract class AbstractGetTokenAction extends ServiceableAction implements ThreadSafe 054{ 055 /** Authentication Token Manager */ 056 protected AuthenticationTokenManager _authenticationTokenManager; 057 058 /** The user population DAO */ 059 protected UserPopulationDAO _userPopulationDAO; 060 061 /** The helper for the associations population/context */ 062 protected PopulationContextHelper _populationContextHelper; 063 064 /** The current user provider */ 065 protected CurrentUserProvider _currentUserProvider; 066 067 /** JSON Utils */ 068 protected JSONUtils _jsonUtils; 069 070 @Override 071 public void service(ServiceManager smanager) throws ServiceException 072 { 073 super.service(smanager); 074 _authenticationTokenManager = (AuthenticationTokenManager) smanager.lookup(AuthenticationTokenManager.ROLE); 075 076 _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE); 077 _populationContextHelper = (PopulationContextHelper) smanager.lookup(PopulationContextHelper.ROLE); 078 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 079 080 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 081 } 082 083 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 084 { 085 Map<String, Object> result = new HashMap<>(); 086 Request request = ObjectModelHelper.getRequest(objectModel); 087 088 String body = null; 089 HttpServletRequest postReq = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); 090 try (InputStream postBody = postReq.getInputStream()) 091 { 092 body = new String(postBody.readAllBytes(), StandardCharsets.UTF_8); 093 } 094 095 String siteName = parameters.getParameter("site"); 096 List<String> populationContexts = List.of("/sites/" + siteName, "/sites-fo/" + siteName); 097 098 Map<String, Object> jsonParams = _jsonUtils.convertJsonToMap(body); 099 100 String population = (String) getParameter("population", jsonParams, request); 101 102 UserIdentity user = null; 103 UserPopulation userPopulation = population != null ? _userPopulationDAO.getUserPopulation(population) : null; 104 105 if (userPopulation != null) 106 { 107 user = authenticate(jsonParams, request, parameters, populationContexts, userPopulation); 108 } 109 else 110 { 111 user = authenticate(jsonParams, request, parameters, populationContexts); 112 } 113 114 if (user != null) 115 { 116 String generateToken = _authenticationTokenManager.generateToken(user, 0, "mobileapp", "Token for the mobile app"); 117 118 result.put("code", 200); 119 result.put("token", generateToken); 120 121 // If the authentication process has lead to an effective session creation, it should be destroyed 122 // Do not provide the redirector. We don't want the request to be redirected 123 _currentUserProvider.logout(null); 124 } 125 else 126 { 127 result.put("code", 403); 128 throw new AccessDeniedException(); 129 } 130 131 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 132 return EMPTY_MAP; 133 } 134 135 /** 136 * Get a parameter from the mobile app, either from the request body or a request parameter 137 * @param name the parameter name 138 * @param jsonParams the decoded JSON request body, if any 139 * @param request the request 140 * @return the parameter value, or null if not found 141 */ 142 protected Object getParameter(String name, Map<String, Object> jsonParams, Request request) 143 { 144 if (jsonParams.containsKey(name)) 145 { 146 return jsonParams.get(name); 147 } 148 else if (request.getParameter(name) != null) 149 { 150 return request.getParameter(name); 151 } 152 else 153 { 154 return request.getAttribute(name); 155 } 156 } 157 158 private UserIdentity authenticate(Map<String, Object> params, Request request, Parameters parameters, List<String> populationContexts) throws ParameterException 159 { 160 String siteName = parameters.getParameter("site"); 161 162 for (String context : populationContexts) 163 { 164 List<UserPopulation> populations = _populationContextHelper.getUserPopulationsOnContexts(List.of(context), false, false).stream() 165 .map(_userPopulationDAO::getUserPopulation) 166 .toList(); 167 168 for (UserPopulation population : populations) 169 { 170 UserIdentity user = _tryConnect(params, request, context, population); 171 if (user != null) 172 { 173 return user; 174 } 175 } 176 } 177 178 getLogger().error("Unable to log in from the mobile application to the '" + siteName + "' site. At least one population should be configured with credential provider compatible with the mobile app."); 179 return null; 180 } 181 182 private UserIdentity authenticate(Map<String, Object> params, Request request, Parameters parameters, List<String> populationContexts, UserPopulation userPopulation) throws ParameterException 183 { 184 String siteName = parameters.getParameter("site"); 185 186 Set<String> contextsForUserPopulation = _populationContextHelper.getContextsForUserPopulation(userPopulation.getId()); 187 188 for (String context : populationContexts) 189 { 190 if (contextsForUserPopulation.contains(context)) 191 { 192 UserIdentity user = _tryConnect(params, request, context, userPopulation); 193 if (user != null) 194 { 195 return user; 196 } 197 } 198 } 199 200 getLogger().error("Unable to log in from the mobile application to the '" + siteName + "' site, with population '" + userPopulation.getId() + "'."); 201 return null; 202 } 203 204 private UserIdentity _tryConnect(Map<String, Object> params, Request request, String context, UserPopulation userPopulation) 205 { 206 List<CredentialProvider> credentialProviders = userPopulation.getCredentialProviders(); 207 208 for (int i = 0; i < credentialProviders.size(); i++) 209 { 210 CredentialProvider credentialProvider = credentialProviders.get(i); 211 UserIdentity user = tryConnect(params, request, context, userPopulation, credentialProvider, i); 212 if (user != null) 213 { 214 return user; 215 } 216 } 217 218 return null; 219 } 220 221 /** 222 * Try to authenticate the current mobile user 223 * @param params the parameters issued by the mobile app 224 * @param request the authentication request 225 * @param context the population context 226 * @param userPopulation the population 227 * @param credentialProvider the {@link CredentialProvider} 228 * @param credentialProviderIndex the index of the credentialProvider in the given userPopulation 229 * @return the authenticated user, if any 230 */ 231 protected abstract UserIdentity tryConnect(Map<String, Object> params, Request request, String context, UserPopulation userPopulation, CredentialProvider credentialProvider, int credentialProviderIndex); 232}