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.plugins.queriesdirectory;
017
018import java.util.ArrayList;
019import java.util.Date;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang3.StringUtils;
032
033import org.ametys.cms.FilterNameHelper;
034import org.ametys.core.group.GroupIdentity;
035import org.ametys.core.right.RightManager;
036import org.ametys.core.right.RightManager.RightResult;
037import org.ametys.core.ui.Callable;
038import org.ametys.core.user.CurrentUserProvider;
039import org.ametys.core.user.UserIdentity;
040import org.ametys.plugins.core.user.UserHelper;
041import org.ametys.plugins.queriesdirectory.Query.QueryProfile;
042import org.ametys.plugins.queriesdirectory.Query.Visibility;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
045import org.ametys.plugins.repository.UnknownAmetysObjectException;
046import org.ametys.runtime.parameter.ParameterHelper;
047import org.ametys.runtime.plugin.component.AbstractLogEnabled;
048
049/**
050 * DAO for manipulating queries
051 */
052public class QueryDAO extends AbstractLogEnabled implements Serviceable, Component
053{
054    /** The Avalon role */
055    public static final String ROLE = QueryDAO.class.getName();
056    
057    /** The current user provider */
058    protected CurrentUserProvider _userProvider;
059    
060    /** The Ametys object resolver */
061    private AmetysObjectResolver _resolver;
062
063    private UserHelper _userHelper;
064
065    private RightManager _rightManager;
066    
067    @Override
068    public void service(ServiceManager serviceManager) throws ServiceException
069    {
070        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
071        _userProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
072        _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE);
073        _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE);
074    }
075    
076    /**
077     * Get queries' properties
078     * @param queryIds The ids of queries to retrieve
079     * @return The queries' properties
080     */
081    @Callable
082    public Map<String, Object> getQueriesProperties(List<String> queryIds)
083    {
084        Map<String, Object> result = new HashMap<>();
085        
086        List<Map<String, Object>> queries = new LinkedList<>();
087        List<Map<String, Object>> notAllowedQueries = new LinkedList<>();
088        Set<String> unknownQueries = new HashSet<>();
089        
090        
091        for (String id : queryIds)
092        {
093            try
094            {
095                Query query = _resolver.resolveById(id);
096                
097                if (_hasRight(query))
098                {
099                    queries.add(getQueryProperties(query));
100                }
101                else
102                {
103                    notAllowedQueries.add(getQueryProperties(query));
104                }
105            }
106            catch (UnknownAmetysObjectException e)
107            {
108                unknownQueries.add(id);
109            }
110        }
111        
112        result.put("queries", queries);
113        result.put("unknownQueries", unknownQueries);
114        result.put("unknownQueries", unknownQueries);
115        
116        return result;
117    }
118    
119    /**
120     * Get the query properties
121     * @param query The query
122     * @return The query properties
123     */
124    public Map<String, Object> getQueryProperties (Query query)
125    {
126        Map<String, Object> infos = new HashMap<>();
127        
128        infos.put("id", query.getId());
129        infos.put("title", query.getTitle());
130        infos.put("type", query.getType());
131        infos.put("description", query.getDescription());
132        infos.put("author", _userHelper.user2json(query.getAuthor())); 
133        infos.put("visibility", query.getVisibility().toString());
134        infos.put("content", query.getContent());
135        infos.put("lastModificationDate", ParameterHelper.valueToString(query.getLastModificationDate()));
136        
137        boolean isAdministrator = isAdministrator();
138        infos.put("isAdministrator", isAdministrator);
139        
140        UserIdentity currentUser = _userProvider.getUser();
141        infos.put("canRead", isAdministrator || query.canRead(currentUser));
142        infos.put("canWrite", isAdministrator || query.canWrite(currentUser));
143        
144        return infos;
145    }
146    
147    /**
148     * Creates a new {@link Query}
149     * @param title The title of the query
150     * @param desc The description of the query
151     * @param type The type of the query
152     * @param content The content of the query
153     * @return The id of created query
154     */
155    @Callable
156    public String createQuery(String title, String desc, String type, String content)
157    {
158        ModifiableTraversableAmetysObject queriesNode = QueryHelper.getQueriesRootNode(_resolver);
159
160        String name = FilterNameHelper.filterName(title);
161        
162        // Find unique name
163        String uniqueName = name;
164        int index = 2;
165        while (queriesNode.hasChild(uniqueName))
166        {
167            uniqueName = name + "-" + (index++);
168        }
169        
170        Query query = queriesNode.createChild(uniqueName, QueryFactory.QUERY_NODETYPE);
171        query.setTitle(title);
172        query.setDescription(desc);
173        query.setAuthor(_userProvider.getUser());
174        query.setType(type);
175        query.setContent(content);
176        query.setVisibility(Visibility.PRIVATE);
177        query.setLastModificationDate(new Date());
178
179        queriesNode.saveChanges();
180
181        return query.getId();
182    }
183    
184    /**
185     * Edits a {@link Query}
186     * @param id The id of the query
187     * @param title The title of the query
188     * @param desc The description of the query
189     * @return A result map
190     */
191    @Callable
192    public Map<String, Object> updateQuery(String id, String title, String desc)
193    {
194        Map<String, Object> results = new HashMap<>();
195        
196        Query query = _resolver.resolveById(id);
197        
198        if (query.canWrite(_userProvider.getUser()))
199        {
200            query.setTitle(title);
201            query.setDescription(desc);
202            query.saveChanges();
203        }
204        else
205        {
206            results.put("message", "not-allowed");
207        }
208        
209        results.put("id", query.getId());
210        
211        return results;
212    }
213    
214    /**
215     * Saves a {@link Query}
216     * @param id The id of the query
217     * @param type The type of the query
218     * @param content The content of the query
219     * @return A result map
220     */
221    @Callable
222    public Map<String, Object> saveQuery(String id, String type, String content)
223    {
224        Map<String, Object> results = new HashMap<>();
225        
226        Query query = _resolver.resolveById(id);
227        
228        if (query.canWrite(_userProvider.getUser()))
229        {
230            query.setType(type);
231            query.setContent(content);
232            query.saveChanges();
233        }
234        else
235        {
236            results.put("message", "not-allowed");
237        }
238        
239        results.put("id", query.getId());
240        
241        return results;
242    }
243    
244    /**
245     * Deletes {@link Query}(ies)
246     * @param ids The ids of the queries to delete
247     * @return A result map
248     */
249    @Callable
250    public Map<String, Object> deleteQuery(List<String> ids)
251    {
252        Map<String, Object> results = new HashMap<>();
253        
254        List<String> deletedQueries = new ArrayList<>();
255        List<String> unknownQueries = new ArrayList<>();
256        List<String> notallowedQueries = new ArrayList<>();
257         
258        for (String id : ids)
259        {
260            try
261            {
262                Query query = _resolver.resolveById(id);
263                
264                UserIdentity author = query.getAuthor();
265                if (author != null && author.equals(_userProvider.getUser()))
266                {
267                    query.remove();
268                    query.saveChanges();
269                    deletedQueries.add(id);
270                }
271                else
272                {
273                    notallowedQueries.add(query.getTitle());
274                }
275            }
276            catch (UnknownAmetysObjectException e)
277            {
278                unknownQueries.add(id);
279                getLogger().error("Unable to delete query. The query of id '" + id + " doesn't exist", e);
280            }
281        }
282        
283        results.put("deletedQueries", deletedQueries);
284        results.put("notallowedQueries", notallowedQueries);
285        results.put("unknownQueries", unknownQueries);
286        
287        return results;
288    }
289    
290    /**
291     * Changes the visibility of a {@link Query}
292     * @param queryId The id of the query
293     * @param visibilityStr The new visibility
294     * @return A result map
295     */
296    @Callable
297    public Map<String, Object> changeVisibility(String queryId, String visibilityStr)
298    {
299        Map<String, Object> results = new HashMap<>();
300        
301        // Parameter checks.
302        Query query = null;
303        if (StringUtils.isNotEmpty(queryId))
304        {
305            query = _resolver.resolveById(queryId);
306        }
307        else
308        {
309            throw new IllegalArgumentException("Mandatory query id parameter is missing.");
310        }
311        
312        Visibility visibility = Visibility.valueOf(visibilityStr.toUpperCase());
313        
314        UserIdentity author = query.getAuthor();
315        if (author != null && author.equals(_userProvider.getUser()))
316        {
317            query.setVisibility(visibility);
318            query.saveChanges();
319        }
320        else
321        {
322            results.put("message", "not-allowed");
323        }
324        
325        return results;
326    }
327    
328    /**
329     * Assign rights to the given users on the given query
330     * @param queryId The query id
331     * @param profileId The profile id
332     * @param users The users to grant
333     * @return A result map
334     */
335    @Callable
336    public Map<String, Object> addGrantedUsers(String queryId, String profileId, List<Map<String, String>> users)
337    {
338        return _assignRights(queryId, profileId, "users", users, null);
339    }
340    
341    /**
342     * Assign rigths to the given groups on the given query
343     * @param queryId The query id
344     * @param profileId The profile id
345     * @param groups The groups to grant
346     * @return A result map
347     */
348    @Callable
349    public Map<String, Object> addGrantedGroups(String queryId, String profileId, List<Map<String, String>> groups)
350    {
351        return _assignRights(queryId, profileId, "groups", null, groups);
352    }
353    
354    private Map<String, Object> _assignRights(String queryId, String profileId, String type, List<Map<String, String>> users, List<Map<String, String>> groups)
355    {
356        HashMap<String, Object> results = new HashMap<>();
357        
358        // Parameter checks.
359        Query query = null;
360        if (StringUtils.isNotEmpty(queryId))
361        {
362            query = _resolver.resolveById(queryId);
363        }
364        else
365        {
366            throw new IllegalArgumentException("Mandatory query id parameter is missing.");
367        }
368        
369        if (!"users".equals(type) && !"groups".equals(type))
370        {
371            throw new IllegalArgumentException("Unexpected type parameter : " + type);
372        }
373        
374        if (!"read_access".equals(profileId) && !"write_access".equals(profileId))
375        {
376            throw new IllegalArgumentException("Unexpected profile identifier : " + profileId);
377        }
378        
379        UserIdentity author = query.getAuthor();
380        if (author != null && author.equals(_userProvider.getUser()))
381        {
382            QueryProfile profile = QueryProfile.valueOf(profileId.toUpperCase());
383            
384            // Set new values
385            if ("users".equals(type))
386            {
387                Set<UserIdentity> allEntries = query.getGrantedUsers(profile);
388                
389                for (Map<String, String> user : users)
390                {
391                    UserIdentity userIdentity = new UserIdentity(user.get("login"), user.get("populationId"));
392                    if (!allEntries.contains(userIdentity))
393                    {
394                        allEntries.add(userIdentity);
395                    }
396                }
397                
398                query.setGrantedUsers(profile, allEntries);
399            }
400            else
401            {
402                Set<GroupIdentity> allEntries = query.getGrantedGroups(profile);
403                
404                for (Map<String, String> group : groups)
405                {
406                    GroupIdentity groupIdentity = new GroupIdentity(group.get("id"), group.get("groupDirectory"));
407                    if (!allEntries.contains(groupIdentity))
408                    {
409                        allEntries.add(groupIdentity);
410                    }
411                }
412                
413                query.setGrantedGroups(profile, allEntries);
414            }
415            
416            // Save
417            query.saveChanges();
418        }
419        else
420        {
421            results.put("message", "not-allowed");
422        }
423        
424        return results;
425    }
426    
427    /**
428     * Remove rights to the given users on the given query
429     * @param queryId The query id
430     * @param profileId The profile id
431     * @param users The users to remove
432     * @param groups The groups to remove
433     * @return A result map
434     */
435    @Callable
436    public Map<String, Object> removeAssignment(String queryId, String profileId, List<Map<String, String>> users, List<Map<String, String>> groups)
437    {
438        HashMap<String, Object> results = new HashMap<>();
439        
440        // Parameter checks.
441        Query query = null;
442        if (StringUtils.isNotEmpty(queryId))
443        {
444            query = _resolver.resolveById(queryId);
445        }
446        else
447        {
448            throw new IllegalArgumentException("Mandatory query id parameter is missing.");
449        }
450        if (!"read_access".equals(profileId) && !"write_access".equals(profileId))
451        {
452            throw new IllegalArgumentException("Unexpected profile identifier : " + profileId);
453        }
454        
455        UserIdentity author = query.getAuthor();
456        if (author != null && author.equals(_userProvider.getUser()))
457        {
458            QueryProfile profile = QueryProfile.valueOf(profileId.toUpperCase());
459            
460            if (!users.isEmpty())
461            {
462                Set<UserIdentity> grantedUsers = query.getGrantedUsers(profile);
463                for (Map<String, String> user : users)
464                {
465                    UserIdentity userIdentity = new UserIdentity(user.get("login"), user.get("populationId"));
466                    grantedUsers.remove(userIdentity);
467                }
468                
469                query.setGrantedUsers(profile, grantedUsers);
470            }
471            
472            if (!groups.isEmpty())
473            {
474                Set<GroupIdentity> grantedGroups = query.getGrantedGroups(profile);
475                for (Map<String, String> group : groups)
476                {
477                    GroupIdentity groupIdentity = new GroupIdentity(group.get("id"), group.get("groupDirectory"));
478                    grantedGroups.remove(groupIdentity);
479                }
480                
481                query.setGrantedGroups(profile, grantedGroups);
482            }
483            
484            // Save
485            query.saveChanges();
486        }
487        else
488        {
489            results.put("message", "not-allowed");
490        }
491        
492        return results;
493    }
494    
495    /**
496     * Determines if the current user is administrator of queries
497     * @return <code>true</code> if current user is administrator 
498     */
499    public boolean isAdministrator()
500    {
501        return _rightManager.hasRight(_userProvider.getUser(), "QueriesDirectory_Rights_Admin", "/cms") == RightResult.RIGHT_ALLOW;
502    }
503    
504    /**
505     * Test if the current user has the right required to access the query
506     * @param query The query
507     * @return true if the user has the right needed, false otherwise.
508     */
509    protected boolean _hasRight(Query query)
510    {
511        UserIdentity user = _userProvider.getUser();
512        return query.canRead(user) || isAdministrator();
513    }
514}