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        _saxUsersStatusInfo();
116        
117        XMLUtils.endElement(contentHandler, "Populations");
118        contentHandler.endDocument();
119    }
120    
121    private void _saxMonitoringInfo() throws SAXException
122    {
123        boolean enabled = Config.getInstance().getValue("cache.monitoring.schedulers.enable");
124        
125        AttributesImpl attrs = new AttributesImpl();
126        attrs.addCDATAAttribute("enabled", enabled ? "true" : "false");
127        XMLUtils.startElement(contentHandler, "Monitoring", attrs);
128
129        if (enabled)
130        {
131            String datasourceId = Config.getInstance().getValue("cache.monitoring.datasource.jdbc.pool");
132            XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
133        }
134        
135        XMLUtils.endElement(contentHandler, "Monitoring");
136    }
137    
138    private void _saxCaptchaInfo() throws SAXException
139    {
140        String type = Config.getInstance().getValue("runtime.captcha.type");
141        
142        AttributesImpl attrs = new AttributesImpl();
143        attrs.addCDATAAttribute("type", type);
144        
145        for (String parameter : CaptchaHelper.getCaptcha().getConfigParameters())
146        {
147            ElementDefinition elementDefinition = (ElementDefinition) ConfigManager.getInstance().getModelItem(parameter);
148            attrs.addCDATAAttribute(parameter, elementDefinition.getType().toString(Config.getInstance().getValue(parameter)));
149        }
150        
151        XMLUtils.createElement(contentHandler, "Captcha", attrs);
152    }
153    
154    private void _saxMaxUploadSizeInfo() throws SAXException
155    {
156        long uploadMaxSize = Config.getInstance().getValue("runtime.upload.max-size");
157        
158        XMLUtils.createElement(contentHandler, "UploadMaxSize", Long.toString(uploadMaxSize));
159    }
160
161    private void _saxDatabasesFiles() throws IOException, SAXException
162    {
163        _saxSQLDatabaseFile();
164
165        _saxLDAPDatabaseFile();
166    }
167
168    private void _saxLDAPDatabaseFile() throws SAXException, IOException
169    {
170        XMLUtils.startElement(contentHandler, "LDAPDatasources");
171
172        File file = _ldapDataSourceManager.getFileConfiguration();
173        if (file.exists())
174        {
175            SAXParser saxParser = null;
176            try (InputStream is = new FileInputStream(file))
177            {
178                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
179                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
180            }
181            catch (FileNotFoundException e)
182            {
183                throw new IOException(e);
184            }
185            catch (ServiceException e)
186            {
187                throw new SAXException("Unable to get a SAX parser", e);
188            }
189            finally
190            {
191                manager.release(saxParser);
192            }
193        }
194
195        XMLUtils.endElement(contentHandler, "LDAPDatasources");
196    }
197
198    private void _saxSQLDatabaseFile() throws SAXException, IOException
199    {
200        XMLUtils.startElement(contentHandler, "SQLDatasources");
201
202        File file = _sqlDataSourceManager.getFileConfiguration();
203        if (file.exists())
204        {
205            SAXParser saxParser = null;
206            try (InputStream is = new FileInputStream(file))
207            {
208                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
209                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
210            }
211            catch (FileNotFoundException e)
212            {
213                throw new IOException(e);
214            }
215            catch (ServiceException e)
216            {
217                throw new SAXException("Unable to get a SAX parser", e);
218            }
219            finally
220            {
221                manager.release(saxParser);
222            }
223        }
224        
225        XMLUtils.endElement(contentHandler, "SQLDatasources");
226    }
227
228    private void _saxPopulationFile() throws SAXException, IOException
229    {
230        XMLUtils.startElement(contentHandler, "UserPopulations");
231        File file = _userPopulationDAO.getConfigurationFile();
232
233        if (file != null && file.exists())
234        {
235            SAXParser saxParser = null;
236            try (InputStream is = Files.newInputStream(Paths.get(file.getAbsolutePath())))
237            {
238                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
239                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
240            }
241            catch (ServiceException e)
242            {
243                throw new SAXException("Unable to get a SAX parser", e);
244            }
245            finally
246            {
247                manager.release(saxParser);
248            }
249        }
250        
251        XMLUtils.endElement(contentHandler, "UserPopulations");
252    }
253
254    private void _saxUsedStuff(Set<UserPopulation> usedPopulations) throws SAXException
255    {
256        XMLUtils.startElement(contentHandler, "InUse");
257        
258        _saxPopulationsInUse(usedPopulations);
259        
260        _saxDatasourcesInUse(usedPopulations);
261
262        XMLUtils.endElement(contentHandler, "InUse");
263    }
264
265    private void _saxDatasourcesInUse(Set<UserPopulation> usedPopulations) throws SAXException
266    {
267        Pair<Set<String>, Map<String, Set<String>>> usedDatasources = _getDatasourcesUsedByPopulations(usedPopulations);
268        
269        XMLUtils.startElement(contentHandler, "Datasources");
270        
271        for (String datasourceId : usedDatasources.getLeft())
272        {
273            XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
274        }
275        
276        XMLUtils.endElement(contentHandler, "Datasources");
277        
278        XMLUtils.startElement(contentHandler, "Datasources-Model");
279        
280        Map<String, Set<String>> datasourcesPerModel = usedDatasources.getRight();
281        for (String modelId : datasourcesPerModel.keySet())
282        {
283            XMLUtils.startElement(contentHandler, modelId);
284            
285            for (String parameter : datasourcesPerModel.get(modelId))
286            {
287                XMLUtils.createElement(contentHandler, parameter);
288            }
289            
290            XMLUtils.endElement(contentHandler, modelId);
291        }
292        
293        XMLUtils.endElement(contentHandler, "Datasources-Model");
294    }
295
296    private void _saxPopulationsInUse(Set<UserPopulation> usedPopulations) throws SAXException
297    {
298        XMLUtils.startElement(contentHandler, "UserPopulations");
299        for (UserPopulation userPopulation : usedPopulations)
300        {
301            XMLUtils.createElement(contentHandler, "UserPopulation", userPopulation.getId());
302        }
303        XMLUtils.endElement(contentHandler, "UserPopulations");
304    }
305    
306    private String _replaceDefaultIds(String datasourceId)
307    {
308        // Default sources won't be default anymore => change it
309        if (_ldapDataSourceManager.getDefaultDataSourceId().equals(datasourceId))
310        {
311            return _ldapDataSourceManager.getDefaultDataSourceDefinition().getId();
312        }
313        else if (_sqlDataSourceManager.getDefaultDataSourceId().equals(datasourceId))
314        {
315            return _sqlDataSourceManager.getDefaultDataSourceDefinition().getId();
316        }
317        else
318        {
319            return datasourceId;
320        }
321    }
322
323    private Set<UserPopulation> _getPopulationsUsedBySites()
324    {
325        // Retrieve the sites to build the contexts to search on
326        Collection<String> siteNames = _siteManager.getSiteNames();
327        
328        // We return all the populations linked to at least one site
329        List<String> contexts = new ArrayList<>();
330        for (String siteName : siteNames)
331        {
332            contexts.add("/sites/" + siteName);
333            contexts.add("/sites-fo/" + siteName);
334        }
335        
336        Set<String> populations = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, false);
337        return populations.stream().map(_userPopulationDAO::getUserPopulation).collect(Collectors.toSet());
338    }
339    
340    private Pair<Set<String>, Map<String, Set<String>>> _getDatasourcesUsedByPopulations(Set<UserPopulation> usedPopulations)
341    {
342        Set<String> datasourcesInUse = new HashSet<>();
343        Map<String, Set<String>> datasourcesPerModel = new HashMap<>();
344        
345        for (UserPopulation userPopulation : usedPopulations)
346        {
347            for (UserDirectory userDirectory : userPopulation.getUserDirectories())
348            {
349                String userDirectoryModelId = userDirectory.getUserDirectoryModelId();
350                UserDirectoryModel userDirectoryModel = _userDirectoryFactory.getExtension(userDirectoryModelId);
351                
352                Map<String, Object> parameterValues = userDirectory.getParameterValues();
353                
354                Map<String, ? extends ElementDefinition> userDirectoryModelParameters = userDirectoryModel.getParameters();
355                for (String userDirectoryModelParameterId : userDirectoryModelParameters.keySet())
356                {
357                    ElementDefinition userDirectoryModelParameter = userDirectoryModelParameters.get(userDirectoryModelParameterId);
358                    if (ModelItemTypeConstants.DATASOURCE_ELEMENT_TYPE_ID.equals(userDirectoryModelParameter.getType().getId()))
359                    {
360                        String datasourceId = (String) parameterValues.get(userDirectoryModelParameterId);
361                        datasourcesInUse.add(datasourceId);
362                        _addDatasourceToModel(userDirectoryModelId, userDirectoryModelParameterId, datasourcesPerModel);
363                    }
364                }
365            }
366            
367            for (CredentialProvider credentialProvider : userPopulation.getCredentialProviders())
368            {
369                String credentialProviderModelId = credentialProvider.getCredentialProviderModelId();
370                CredentialProviderModel credentialProviderModel = _credentialProviderFactory.getExtension(credentialProviderModelId);
371                
372                Map<String, Object> parameterValues = credentialProvider.getParameterValues();
373                
374                Map<String, ? extends ElementDefinition> credentialProviderModelParameters = credentialProviderModel.getParameters();
375                for (String credentialProviderParameterId : credentialProviderModelParameters.keySet())
376                {
377                    ElementDefinition credentialProviderModelParameter = credentialProviderModelParameters.get(credentialProviderParameterId);
378                    if (ModelItemTypeConstants.DATASOURCE_ELEMENT_TYPE_ID.equals(credentialProviderModelParameter.getType().getId()))
379                    {
380                        String datasourceId = (String) parameterValues.get(credentialProviderParameterId);
381                        datasourcesInUse.add(datasourceId);
382                        _addDatasourceToModel(credentialProviderModelId, credentialProviderParameterId, datasourcesPerModel);
383                    }
384                }
385            }
386        }
387        
388        return Pair.of(datasourcesInUse, datasourcesPerModel);
389    }
390    
391    private void _addDatasourceToModel(String modelId, String datasource, Map<String, Set<String>> datasourcesPerModel)
392    {
393        Set<String> datasources = datasourcesPerModel.get(modelId);
394        if (datasources == null)
395        {
396            datasources = new HashSet<>();
397            datasourcesPerModel.put(modelId, datasources);
398        }
399        
400        datasources.add(datasource);
401    }
402    
403    private void _saxPepperFiles(Set<String> userDirectoryIds) throws SAXException, IOException
404    {
405        XMLUtils.startElement(contentHandler, "Peppers");
406        File pepperFolder = new File(AmetysHomeHelper.getAmetysHomeData(), "auth");
407
408        if (pepperFolder.exists())
409        {
410            for (File pepperFile : pepperFolder.listFiles((file, name) -> name.startsWith("pepper_")))
411            {
412                String fileName = pepperFile.getName();
413                
414                String userDirectoryId = fileName.replace("pepper_", "");
415                
416                if (userDirectoryIds.contains(userDirectoryId))
417                {
418                    try (InputStream is = new FileInputStream(pepperFile))
419                    {
420                        byte[] pepperAsByteArray = IOUtils.toByteArray(is);
421                        
422                        String pepper = Base64.getEncoder().encodeToString(pepperAsByteArray);
423                        XMLUtils.createElement(contentHandler, fileName, pepper);
424                    }
425                }
426            }
427        }
428        
429        XMLUtils.endElement(contentHandler, "Peppers");
430    }
431    
432    private void _saxMulitfactorAuthentication(Set<String> userPopulationIds) throws SAXException, IOException
433    {
434        XMLUtils.startElement(contentHandler, "MultifactorAuthentication");
435
436        // Generate SAX events for the datasource configuration parameter
437        String datasourceId = Config.getInstance().getValue("runtime.assignments.multifactorauthentication");
438        XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
439        
440        // Generate SAX events for files containing keys to encrypt/decrypt secrets
441        File mfaFolder = new File(AmetysHomeHelper.getAmetysHomeConfig(), "mfa");
442        if (mfaFolder.exists())
443        {
444            for (File mfaFile : mfaFolder.listFiles((file, name) -> name.startsWith("mfa_")))
445            {
446                String fileName = mfaFile.getName();
447                
448                String userPopulationId = fileName.replace("mfa_", "");
449                
450                if (userPopulationIds.contains(userPopulationId))
451                {
452                    try (InputStream is = new FileInputStream(mfaFile))
453                    {
454                        byte[] mfaKeyAsByteArray = IOUtils.toByteArray(is);
455                        
456                        String mfaKey = Base64.getEncoder().encodeToString(mfaKeyAsByteArray);
457                        XMLUtils.createElement(contentHandler, fileName, mfaKey);
458                    }
459                }
460            }
461        }
462        
463        XMLUtils.endElement(contentHandler, "MultifactorAuthentication");
464    }
465    
466    private void _saxUsersStatusInfo() throws SAXException
467    {
468        XMLUtils.startElement(contentHandler, "UsersStatus");
469        
470        String datasourceId = Config.getInstance().getValue("runtime.users.status.datasource");
471        XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
472        
473        XMLUtils.endElement(contentHandler, "UsersStatus");
474    }
475
476}