001/*
002 *  Copyright 2023 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.core.right;
017
018import java.util.ArrayList;
019import java.util.Comparator;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Objects;
026import java.util.Set;
027
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030
031import org.ametys.core.right.AccessController.ExplanationObject;
032import org.ametys.core.right.AccessController.Permission;
033import org.ametys.core.ui.Callable;
034import org.ametys.core.ui.StaticClientSideElement;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.core.user.UserManager;
037import org.ametys.plugins.core.user.UserHelper;
038import org.ametys.runtime.i18n.I18nizableText;
039
040/**
041 * Client side element of the tool displaying all the permissions for a given user
042 */
043public class UserPermissionsToolClientSideElement extends StaticClientSideElement
044{
045
046    private UserHelper _userHelper;
047    private UserManager _userManager;
048    private RightProfilesDAO _profileDAO;
049    private RightsExtensionPoint _rightsEP;
050
051    @Override
052    public void service(ServiceManager manager) throws ServiceException
053    {
054        super.service(manager);
055        _profileDAO = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE);
056        _rightsEP = (RightsExtensionPoint) manager.lookup(RightsExtensionPoint.ROLE);
057        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
058        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
059    }
060    
061    /**
062     * Provided a user identity, this method will retrieve all permissions concerning this user
063     * ie permissions for anonymous, any connected user, user's groups, and the user itself
064     * @param login the user login
065     * @param populationId the user population
066     * @return a map with the key "data" containing the list of all the contexts and their permissions.
067     * And a key metaData containing a map used to reconfigure the store calling the callable
068     * 
069     * The list of context is something like :
070     * [
071     *      {
072     *          label: "foo",
073     *          category: "bar",
074     *          order: 0,
075     *          <permissionKey>: {
076     *              assignments: [accessResultInfo,…]
077     *              accessResult: accessResult,
078     *          }
079     *      }
080     * ]
081     */
082    @Callable(rights = "Runtime_Rights_SeeUserProfiles")
083    public Map<String, Object> getUserPermissions(String login, String populationId)
084    {
085        List<Map<String, Object>> result = new ArrayList<>();
086        
087        UserIdentity user = new UserIdentity(login, populationId);
088        
089        Map<ExplanationObject, Map<Permission, List<AccessExplanation>>> permissionsForUser = _rightManager.getAllPermissions(user);
090        
091        if (permissionsForUser.isEmpty())
092        {
093            return Map.of();
094        }
095        
096        Set<Permission> allPermissions = new HashSet<>();
097        
098        for (ExplanationObject context: permissionsForUser.keySet())
099        {
100            allPermissions.addAll(permissionsForUser.get(context).keySet());
101            
102            Map<String, Object> jsonAssignments = _contextPermissionsToJSON(permissionsForUser.get(context));
103            
104            jsonAssignments.put("label", context.label());
105            jsonAssignments.put("category", context.category());
106            jsonAssignments.put("order", context.order());
107            
108            result.add(jsonAssignments);
109        }
110        
111        List<Map<String, Object>> jsonPermissions = allPermissions.stream()
112                .map(this::_permissionToJSON)
113                .filter(Objects::nonNull)
114                .toList();
115        
116        return Map.of(
117                "data", result,
118                "metaData", Map.of(
119                    "permissions", jsonPermissions
120                )
121            );
122    }
123    
124    /**
125     * Take a map associating permissions with a list of explanations
126     * and serialize it into a JSON object organized by permission,
127     * storing the resulting access result for this permission
128     * and the list of each access explanation
129     * {
130     *      profile1 : {
131     *          accessResult: &lt;the merge result of all the AccessResult targeting the profile&gt;
132     *          assignments: [
133     *              {
134     *                  accessResult: …
135     *                  target: …
136     *              },
137     *              …
138     *          ]
139     *      },…
140     * @param contextPermissions a map of all the permissions on a context
141     * @return a JSON object representing those permissions
142     */
143    private Map<String, Object> _contextPermissionsToJSON(Map<Permission, List<AccessExplanation>> contextPermissions)
144    {
145        // assignments are now organized the way we want for the result
146        // we now need to JSONify it
147        Map<String, Object> result = new HashMap<>();
148        for (Entry<Permission, List<AccessExplanation>> entry : contextPermissions.entrySet())
149        {
150            List<AccessExplanation> explanations = entry.getValue();
151            if (!explanations.isEmpty())
152            {
153                // sort to have user > group > any connected > anonymous
154                explanations.sort(Comparator.naturalOrder());
155                
156                Permission permission = entry.getKey();
157                result.put(
158                        permission.toString(),
159                        Map.of(
160                                "accessResult", explanations.get(0).accessResult(), // list is sorted so first item is merge result
161                                "accessExplanations", explanations
162                        )
163                );
164            }
165        }
166        return result;
167    }
168
169    /**
170     * Get user's information
171     * @param login The user's login
172     * @param populationId The id of the population
173     * @return The user's information
174     */
175    @Callable(rights = "Runtime_Rights_SeeUserProfiles")
176    public Map<String, Object> getUser (String login, String populationId)
177    {
178        return _userHelper.user2json(_userManager.getUser(populationId, login), true);
179    }
180
181    private Map<String, Object> _permissionToJSON(Permission permission)
182    {
183        switch (permission.type())
184        {
185            case READ:
186                return Map.of(
187                    "key", permission.toString(),
188                    "type", permission.type().name(),
189                    "label", new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHTS_READER_LABEL"),
190                    "rights", List.of()
191                );
192            case ALL_RIGHTS:
193                return Map.of(
194                        "key", permission.toString(),
195                        "type", permission.type().name(),
196                        "label", new I18nizableText("plugin.core-ui", "PLUGINS_CORE_UI_TOOL_USER_PROFILES_ALL_RIGHTS_COLUMN_LABEL"),
197                        "rights", List.of()
198                    );
199            case PROFILE:
200                Profile profile = _profileDAO.getProfile(permission.id());
201                if (profile != null)
202                {
203                    return Map.of(
204                            "key", permission.toString(),
205                            "id", permission.id(),
206                            "type", permission.type().name(),
207                            "label", profile.getLabel(),
208                            "rights", _profileDAO.getRights(permission.id())
209                    );
210                }
211                else // ignore unknown profiles
212                {
213                    getLogger().info("No profile with id '" + permission.id() + "'. The permission is ignored.");
214                    return null;
215                }
216            case RIGHT:
217                Right right = _rightsEP.getExtension(permission.id());
218                if (right != null)
219                {
220                    return Map.of(
221                            "key", permission.toString(),
222                            "id", permission.id(),
223                            "type", permission.type().name(),
224                            "label", right.getLabel(),
225                            "rights", List.of(permission.id())
226                        );
227                }
228                else // ignore unknown rights
229                {
230                    getLogger().info("No right with id '" + permission.id() + "'. The permission is ignored.");
231                    return null;
232                }
233            default:
234                return null;
235        }
236    }
237
238}