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.web.site;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.io.InputStream;
023import java.nio.file.Files;
024import java.nio.file.Paths;
025import java.util.ArrayList;
026import java.util.Base64;
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033import java.util.stream.Collectors;
034
035import org.apache.avalon.framework.service.ServiceException;
036import org.apache.avalon.framework.service.ServiceManager;
037import org.apache.cocoon.ProcessingException;
038import org.apache.cocoon.generation.ServiceableGenerator;
039import org.apache.cocoon.xml.AttributesImpl;
040import org.apache.cocoon.xml.XMLUtils;
041import org.apache.commons.io.IOUtils;
042import org.apache.commons.lang3.tuple.Pair;
043import org.apache.excalibur.xml.sax.SAXParser;
044import org.xml.sax.InputSource;
045import org.xml.sax.SAXException;
046
047import org.ametys.core.authentication.CredentialProvider;
048import org.ametys.core.authentication.CredentialProviderFactory;
049import org.ametys.core.authentication.CredentialProviderModel;
050import org.ametys.core.captcha.CaptchaHelper;
051import org.ametys.core.datasource.LDAPDataSourceManager;
052import org.ametys.core.datasource.SQLDataSourceManager;
053import org.ametys.core.user.directory.UserDirectory;
054import org.ametys.core.user.directory.UserDirectoryFactory;
055import org.ametys.core.user.directory.UserDirectoryModel;
056import org.ametys.core.user.population.PopulationContextHelper;
057import org.ametys.core.user.population.UserPopulation;
058import org.ametys.core.user.population.UserPopulationDAO;
059import org.ametys.core.util.IgnoreRootHandler;
060import org.ametys.runtime.config.Config;
061import org.ametys.runtime.config.ConfigManager;
062import org.ametys.runtime.model.ElementDefinition;
063import org.ametys.runtime.model.type.ModelItemTypeConstants;
064import org.ametys.runtime.util.AmetysHomeHelper;
065import org.ametys.web.repository.site.SiteManager;
066
067/**
068 * Sax the datasources files limited to datasources useful for front-office
069 */
070public class SitesPopulationsGenerator extends ServiceableGenerator
071{
072    private SiteManager _siteManager;
073    private PopulationContextHelper _populationContextHelper;
074    private UserPopulationDAO _userPopulationDAO;
075    private UserDirectoryFactory _userDirectoryFactory;
076    private CredentialProviderFactory _credentialProviderFactory;
077    private SQLDataSourceManager _sqlDataSourceManager;
078    private LDAPDataSourceManager _ldapDataSourceManager;
079
080    @Override
081    public void service(ServiceManager smanager) throws ServiceException
082    {
083        super.service(smanager);
084        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
085        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
086        _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE);
087        _userDirectoryFactory = (UserDirectoryFactory) manager.lookup(UserDirectoryFactory.ROLE);
088        _credentialProviderFactory = (CredentialProviderFactory) manager.lookup(CredentialProviderFactory.ROLE);
089        _sqlDataSourceManager = (SQLDataSourceManager) manager.lookup(SQLDataSourceManager.ROLE);
090        _ldapDataSourceManager = (LDAPDataSourceManager) manager.lookup(LDAPDataSourceManager.ROLE);
091    }
092    
093    public void generate() throws IOException, SAXException, ProcessingException
094    {
095        contentHandler.startDocument();
096        XMLUtils.startElement(contentHandler, "Populations");
097        
098        Set<UserPopulation> usedPopulations = _getPopulationsUsedBySites();
099        
100        _saxUsedStuff(usedPopulations);
101        _saxPopulationFile();
102        _saxDatabasesFiles();
103        _saxMonitoringInfo();
104        _saxCaptchaInfo();
105        _saxMaxUploadSizeInfo();
106        
107        _saxPepperFiles(usedPopulations.stream()
108                                       .map(UserPopulation::getUserDirectories)
109                                       .flatMap(Collection::stream)
110                                       .map(UserDirectory::getId)
111                                       .collect(Collectors.toSet()));
112        _saxMulitfactorAuthentication(usedPopulations.stream()
113                                                     .map(UserPopulation::getId)
114                                                     .collect(Collectors.toSet()));
115        
116        XMLUtils.endElement(contentHandler, "Populations");
117        contentHandler.endDocument();
118    }
119    
120    private void _saxMonitoringInfo() throws SAXException
121    {
122        boolean enabled = Config.getInstance().getValue("cache.monitoring.schedulers.enable");
123        
124        AttributesImpl attrs = new AttributesImpl();
125        attrs.addCDATAAttribute("enabled", enabled ? "true" : "false");
126        XMLUtils.startElement(contentHandler, "Monitoring", attrs);
127
128        if (enabled)
129        {
130            String datasourceId = Config.getInstance().getValue("cache.monitoring.datasource.jdbc.pool");
131            XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
132        }
133        
134        XMLUtils.endElement(contentHandler, "Monitoring");
135    }
136    
137    private void _saxCaptchaInfo() throws SAXException
138    {
139        String type = Config.getInstance().getValue("runtime.captcha.type");
140        
141        AttributesImpl attrs = new AttributesImpl();
142        attrs.addCDATAAttribute("type", type);
143        
144        for (String parameter : CaptchaHelper.getCaptcha().getConfigParameters())
145        {
146            ElementDefinition elementDefinition = (ElementDefinition) ConfigManager.getInstance().getModelItem(parameter);
147            attrs.addCDATAAttribute(parameter, elementDefinition.getType().toString(Config.getInstance().getValue(parameter)));
148        }
149        
150        XMLUtils.createElement(contentHandler, "Captcha", attrs);
151    }
152    
153    private void _saxMaxUploadSizeInfo() throws SAXException
154    {
155        long uploadMaxSize = Config.getInstance().getValue("runtime.upload.max-size");
156        
157        XMLUtils.createElement(contentHandler, "UploadMaxSize", Long.toString(uploadMaxSize));
158    }
159
160    private void _saxDatabasesFiles() throws IOException, SAXException
161    {
162        _saxSQLDatabaseFile();
163
164        _saxLDAPDatabaseFile();
165    }
166
167    private void _saxLDAPDatabaseFile() throws SAXException, IOException
168    {
169        XMLUtils.startElement(contentHandler, "LDAPDatasources");
170
171        File file = _ldapDataSourceManager.getFileConfiguration();
172        if (file.exists())
173        {
174            SAXParser saxParser = null;
175            try (InputStream is = new FileInputStream(file))
176            {
177                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
178                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
179            }
180            catch (FileNotFoundException e)
181            {
182                throw new IOException(e);
183            }
184            catch (ServiceException e)
185            {
186                throw new SAXException("Unable to get a SAX parser", e);
187            }
188            finally
189            {
190                manager.release(saxParser);
191            }
192        }
193
194        XMLUtils.endElement(contentHandler, "LDAPDatasources");
195    }
196
197    private void _saxSQLDatabaseFile() throws SAXException, IOException
198    {
199        XMLUtils.startElement(contentHandler, "SQLDatasources");
200
201        File file = _sqlDataSourceManager.getFileConfiguration();
202        if (file.exists())
203        {
204            SAXParser saxParser = null;
205            try (InputStream is = new FileInputStream(file))
206            {
207                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
208                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
209            }
210            catch (FileNotFoundException e)
211            {
212                throw new IOException(e);
213            }
214            catch (ServiceException e)
215            {
216                throw new SAXException("Unable to get a SAX parser", e);
217            }
218            finally
219            {
220                manager.release(saxParser);
221            }
222        }
223        
224        XMLUtils.endElement(contentHandler, "SQLDatasources");
225    }
226
227    private void _saxPopulationFile() throws SAXException, IOException
228    {
229        XMLUtils.startElement(contentHandler, "UserPopulations");
230        File file = _userPopulationDAO.getConfigurationFile();
231
232        if (file != null && file.exists())
233        {
234            SAXParser saxParser = null;
235            try (InputStream is = Files.newInputStream(Paths.get(file.getAbsolutePath())))
236            {
237                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
238                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
239            }
240            catch (ServiceException e)
241            {
242                throw new SAXException("Unable to get a SAX parser", e);
243            }
244            finally
245            {
246                manager.release(saxParser);
247            }
248        }
249        
250        XMLUtils.endElement(contentHandler, "UserPopulations");
251    }
252
253    private void _saxUsedStuff(Set<UserPopulation> usedPopulations) throws SAXException
254    {        
255        XMLUtils.startElement(contentHandler, "InUse");
256        
257        _saxPopulationsInUse(usedPopulations);
258        
259        _saxDatasourcesInUse(usedPopulations);
260
261        XMLUtils.endElement(contentHandler, "InUse");
262    }
263
264    private void _saxDatasourcesInUse(Set<UserPopulation> usedPopulations) throws SAXException
265    {
266        Pair<Set<String>, Map<String, Set<String>>> usedDatasources = _getDatasourcesUsedByPopulations(usedPopulations);
267        
268        XMLUtils.startElement(contentHandler, "Datasources");
269        
270        for (String datasourceId : usedDatasources.getLeft())
271        {
272            XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
273        }
274        
275        XMLUtils.endElement(contentHandler, "Datasources");
276        
277        XMLUtils.startElement(contentHandler, "Datasources-Model");
278        
279        Map<String, Set<String>> datasourcesPerModel = usedDatasources.getRight();
280        for (String modelId : datasourcesPerModel.keySet())
281        {
282            XMLUtils.startElement(contentHandler, modelId);
283            
284            for (String parameter : datasourcesPerModel.get(modelId))
285            {
286                XMLUtils.createElement(contentHandler, parameter);
287            }
288            
289            XMLUtils.endElement(contentHandler, modelId);
290        }
291        
292        XMLUtils.endElement(contentHandler, "Datasources-Model");
293    }
294
295    private void _saxPopulationsInUse(Set<UserPopulation> usedPopulations) throws SAXException
296    {
297        XMLUtils.startElement(contentHandler, "UserPopulations");
298        for (UserPopulation userPopulation : usedPopulations)
299        {
300            XMLUtils.createElement(contentHandler, "UserPopulation", userPopulation.getId());
301        }
302        XMLUtils.endElement(contentHandler, "UserPopulations");
303    }
304    
305    private String _replaceDefaultIds(String datasourceId)
306    {
307        // Default sources won't be default anymore => change it
308        if (_ldapDataSourceManager.getDefaultDataSourceId().equals(datasourceId))
309        {
310            return _ldapDataSourceManager.getDefaultDataSourceDefinition().getId();
311        }
312        else if (_sqlDataSourceManager.getDefaultDataSourceId().equals(datasourceId))
313        {
314            return _sqlDataSourceManager.getDefaultDataSourceDefinition().getId();
315        }
316        else
317        {
318            return datasourceId;
319        }
320    }
321
322    private Set<UserPopulation> _getPopulationsUsedBySites()
323    {
324        // Retrieve the sites to build the contexts to search on
325        Collection<String> siteNames = _siteManager.getSiteNames();
326        
327        // We return all the populations linked to at least one site
328        List<String> contexts = new ArrayList<>();
329        for (String siteName : siteNames)
330        {
331            contexts.add("/sites/" + siteName);
332            contexts.add("/sites-fo/" + siteName);
333        }
334        
335        Set<String> populations = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, false);
336        return populations.stream().map(_userPopulationDAO::getUserPopulation).collect(Collectors.toSet());
337    }
338    
339    private Pair<Set<String>, Map<String, Set<String>>> _getDatasourcesUsedByPopulations(Set<UserPopulation> usedPopulations)
340    {
341        Set<String> datasourcesInUse = new HashSet<>();
342        Map<String, Set<String>> datasourcesPerModel = new HashMap<>();
343        
344        for (UserPopulation userPopulation : usedPopulations)
345        {
346            for (UserDirectory userDirectory : userPopulation.getUserDirectories())
347            {
348                String userDirectoryModelId = userDirectory.getUserDirectoryModelId();
349                UserDirectoryModel userDirectoryModel = _userDirectoryFactory.getExtension(userDirectoryModelId);
350                
351                Map<String, Object> parameterValues = userDirectory.getParameterValues();
352                
353                Map<String, ? extends ElementDefinition> userDirectoryModelParameters = userDirectoryModel.getParameters();
354                for (String userDirectoryModelParameterId : userDirectoryModelParameters.keySet())
355                {
356                    ElementDefinition userDirectoryModelParameter = userDirectoryModelParameters.get(userDirectoryModelParameterId);
357                    if (ModelItemTypeConstants.DATASOURCE_ELEMENT_TYPE_ID.equals(userDirectoryModelParameter.getType().getId()))
358                    {
359                        String datasourceId = (String) parameterValues.get(userDirectoryModelParameterId);
360                        datasourcesInUse.add(datasourceId);
361                        _addDatasourceToModel(userDirectoryModelId, userDirectoryModelParameterId, datasourcesPerModel);
362                    }
363                }
364            }
365            
366            for (CredentialProvider credentialProvider : userPopulation.getCredentialProviders())
367            {
368                String credentialProviderModelId = credentialProvider.getCredentialProviderModelId();
369                CredentialProviderModel credentialProviderModel = _credentialProviderFactory.getExtension(credentialProviderModelId);
370                
371                Map<String, Object> parameterValues = credentialProvider.getParameterValues();
372                
373                Map<String, ? extends ElementDefinition> credentialProviderModelParameters = credentialProviderModel.getParameters();
374                for (String credentialProviderParameterId : credentialProviderModelParameters.keySet())
375                {
376                    ElementDefinition credentialProviderModelParameter = credentialProviderModelParameters.get(credentialProviderParameterId);
377                    if (ModelItemTypeConstants.DATASOURCE_ELEMENT_TYPE_ID.equals(credentialProviderModelParameter.getType().getId()))
378                    {
379                        String datasourceId = (String) parameterValues.get(credentialProviderParameterId);
380                        datasourcesInUse.add(datasourceId);
381                        _addDatasourceToModel(credentialProviderModelId, credentialProviderParameterId, datasourcesPerModel);
382                    }
383                }
384            }
385        }
386        
387        return Pair.of(datasourcesInUse, datasourcesPerModel);
388    }
389    
390    private void _addDatasourceToModel(String modelId, String datasource, Map<String, Set<String>> datasourcesPerModel)
391    {
392        Set<String> datasources = datasourcesPerModel.get(modelId);
393        if (datasources == null)
394        {
395            datasources = new HashSet<>(); 
396            datasourcesPerModel.put(modelId, datasources);
397        }
398        
399        datasources.add(datasource);
400    }
401    
402    private void _saxPepperFiles(Set<String> userDirectoryIds) throws SAXException, IOException
403    {
404        XMLUtils.startElement(contentHandler, "Peppers");
405        File pepperFolder = new File(AmetysHomeHelper.getAmetysHomeData(), "auth");
406
407        if (pepperFolder.exists())
408        {
409            for (File pepperFile : pepperFolder.listFiles((file, name) -> name.startsWith("pepper_")))
410            {
411                String fileName = pepperFile.getName();
412                
413                String userDirectoryId = fileName.replace("pepper_", "");
414                
415                if (userDirectoryIds.contains(userDirectoryId))
416                {
417                    try (InputStream is = new FileInputStream(pepperFile))
418                    {
419                        byte[] pepperAsByteArray = IOUtils.toByteArray(is);
420                        
421                        String pepper = Base64.getEncoder().encodeToString(pepperAsByteArray);
422                        XMLUtils.createElement(contentHandler, fileName, pepper);
423                    }
424                }
425            }
426        }
427        
428        XMLUtils.endElement(contentHandler, "Peppers");
429    }
430    
431    private void _saxMulitfactorAuthentication(Set<String> userPopulationIds) throws SAXException, IOException
432    {
433        XMLUtils.startElement(contentHandler, "MultifactorAuthentication");
434
435        // Generate SAX events for the datasource configuration parameter
436        String datasourceId = Config.getInstance().getValue("runtime.assignments.multifactorauthentication");
437        XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
438        
439        // Generate SAX events for files containing keys to encrypt/decrypt secrets
440        File mfaFolder = new File(AmetysHomeHelper.getAmetysHomeConfig(), "mfa");
441        if (mfaFolder.exists())
442        {
443            for (File mfaFile : mfaFolder.listFiles((file, name) -> name.startsWith("mfa_")))
444            {
445                String fileName = mfaFile.getName();
446                
447                String userPopulationId = fileName.replace("mfa_", "");
448                
449                if (userPopulationIds.contains(userPopulationId))
450                {
451                    try (InputStream is = new FileInputStream(mfaFile))
452                    {
453                        byte[] mfaKeyAsByteArray = IOUtils.toByteArray(is);
454                        
455                        String mfaKey = Base64.getEncoder().encodeToString(mfaKeyAsByteArray);
456                        XMLUtils.createElement(contentHandler, fileName, mfaKey);
457                    }
458                }
459            }
460        }
461        
462        XMLUtils.endElement(contentHandler, "MultifactorAuthentication");
463    }
464}