001/*
002 * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others.
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 *
016 * Contributors:
017 *     Thomas Roger
018 *     Yannis JULIENNE
019 */
020package org.nuxeo.functionaltests;
021
022import static org.nuxeo.functionaltests.AbstractTest.NUXEO_URL;
023import static org.nuxeo.functionaltests.Constants.ADMINISTRATOR;
024
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Calendar;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.stream.Collectors;
035
036import org.apache.commons.lang.StringUtils;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.codehaus.jackson.JsonNode;
040import org.codehaus.jackson.map.ObjectMapper;
041import org.nuxeo.client.api.NuxeoClient;
042import org.nuxeo.client.api.objects.Document;
043import org.nuxeo.client.api.objects.Documents;
044import org.nuxeo.client.api.objects.acl.ACE;
045import org.nuxeo.client.api.objects.user.Group;
046import org.nuxeo.client.internals.spi.NuxeoClientException;
047import org.nuxeo.common.utils.URIUtils;
048
049import okhttp3.Response;
050import okhttp3.ResponseBody;
051
052/**
053 * @since 8.3
054 */
055public class RestHelper {
056
057    private static final NuxeoClient CLIENT = new NuxeoClient(NUXEO_URL, ADMINISTRATOR, ADMINISTRATOR);
058
059    private static final String USER_WORKSPACE_PATH_FORMAT = "/default-domain/UserWorkspaces/%s";
060
061    private static final String DEFAULT_USER_EMAIL = "devnull@nuxeo.com";
062
063    private static final String DOCUMENT_QUERY_BY_PATH_BASE = "SELECT * FROM Document WHERE ecm:path = '%s'";
064
065    private static final List<String> documentIdsToDelete = new ArrayList<>();
066
067    private static final List<String> documentPathsToDelete = new ArrayList<>();
068
069    private static final List<String> usersToDelete = new ArrayList<>();
070
071    private static final List<String> groupsToDelete = new ArrayList<>();
072
073    protected static final Map<String, Set<String>> directoryEntryIdsToDelete = new HashMap<>();
074
075    private static final int NOT_FOUND_ERROR_STATUS = 404;
076
077    protected static final Log log = LogFactory.getLog(RestHelper.class);
078
079    // @yannis : temporary fix for setting user password before JAVACLIENT-91
080    private static final ObjectMapper MAPPER = new ObjectMapper();
081
082    private RestHelper() {
083        // helper class
084    }
085
086    public static void cleanup() {
087        cleanupDocuments();
088        cleanupUsers();
089        cleanupGroups();
090        cleanupDirectoryEntries();
091    }
092
093    public static void cleanupDocuments() {
094        documentIdsToDelete.forEach(RestHelper::deleteDocument);
095        documentIdsToDelete.clear();
096        documentPathsToDelete.clear();
097    }
098
099    public static void cleanupUsers() {
100        for (String user : usersToDelete) {
101            RestHelper.deleteDocument(String.format(USER_WORKSPACE_PATH_FORMAT, user));
102        }
103        usersToDelete.forEach(RestHelper::deleteUser);
104        usersToDelete.clear();
105    }
106
107    public static void cleanupGroups() {
108        groupsToDelete.forEach(RestHelper::deleteGroup);
109        groupsToDelete.clear();
110    }
111
112    public static void cleanupDirectoryEntries() {
113        directoryEntryIdsToDelete.forEach((directoryName, entryIds) -> {
114            entryIds.forEach(id -> deleteDirectoryEntry(directoryName, id));
115        });
116        clearDirectoryEntryIdsToDelete();
117    }
118
119    public static String createUser(String username, String password) {
120        return createUser(username, password, null, null, null, null, null);
121    }
122
123    public static String createUser(String username, String password, String firstName, String lastName, String company,
124            String email, String group) {
125
126        String finalEmail = StringUtils.isBlank(email) ? DEFAULT_USER_EMAIL : email;
127
128        // @yannis : temporary fix for setting user password before JAVACLIENT-91
129        String json = buildUserJSON(username, password, firstName, lastName, company, finalEmail, group);
130
131        Response response = CLIENT.post(AbstractTest.NUXEO_URL + "/api/v1/user", json);
132        if (!response.isSuccessful()) {
133            throw new RuntimeException(String.format("Unable to create user '%s'", username));
134        }
135
136        try (ResponseBody responseBody = response.body()) {
137            JsonNode jsonNode = MAPPER.readTree(responseBody.charStream());
138            String id = jsonNode.get("id").getTextValue();
139            usersToDelete.add(id);
140            return id;
141        } catch (IOException e) {
142            throw new RuntimeException(e);
143        }
144    }
145
146    public static void addUserToDelete(String userName) {
147        usersToDelete.add(userName);
148    }
149
150    public static void removeUserToDelete(String userName) {
151        usersToDelete.remove(userName);
152    }
153
154    public static void addDirectoryEntryToDelete(String directoryName, String entryId) {
155        directoryEntryIdsToDelete.computeIfAbsent(directoryName, k -> new HashSet<>()).add(entryId);
156    }
157
158    public static void clearDirectoryEntryIdsToDelete() {
159        directoryEntryIdsToDelete.clear();
160    }
161
162    private static String buildUserJSON(String username, String password, String firstName, String lastName,
163            String company, String email, String group) {
164        StringBuilder sb = new StringBuilder();
165        sb.append("{");
166        sb.append("\"entity-type\": \"user\"").append(",\n");
167        sb.append("\"id\": \"").append(username).append("\",\n");
168        sb.append("\"properties\": {").append("\n");
169        if (firstName != null) {
170            sb.append("\"firstName\": \"").append(firstName).append("\",\n");
171        }
172        if (lastName != null) {
173            sb.append("\"lastName\": \"").append(lastName).append("\",\n");
174        }
175        if (email != null) {
176            sb.append("\"email\": \"").append(email).append("\",\n");
177        }
178        if (company != null) {
179            sb.append("\"company\": \"").append(company).append("\",\n");
180        }
181        if (group != null) {
182            sb.append("\"groups\": [\"").append(group).append("\"]").append(",\n");
183        }
184        sb.append("\"username\": \"").append(username).append("\",\n");
185        sb.append("\"password\": \"").append(password).append("\"\n");
186        sb.append("}").append("\n");
187        sb.append("}");
188        return sb.toString();
189    }
190
191    public static void deleteUser(String username) {
192        try {
193            CLIENT.getUserManager().deleteUser(username);
194        } catch (NuxeoClientException e) {
195            if (NOT_FOUND_ERROR_STATUS == e.getStatus()) {
196                log.warn(String.format("User %s not deleted because not found", username));
197            } else {
198                throw e;
199            }
200        }
201    }
202
203    public static void createGroup(String name, String label) {
204        createGroup(name, label, null, null);
205    }
206
207    public static void createGroup(String name, String label, String[] members, String[] subGroups) {
208        Group group = new Group();
209        group.setGroupName(name);
210        group.setGroupLabel(label);
211        if (members != null) {
212            group.setMemberUsers(Arrays.asList(members));
213        }
214        if (subGroups != null) {
215            group.setMemberGroups(Arrays.asList(subGroups));
216        }
217
218        CLIENT.getUserManager().createGroup(group);
219        groupsToDelete.add(name);
220    }
221
222    public static void deleteGroup(String name) {
223        try {
224            CLIENT.getUserManager().deleteGroup(name);
225        } catch (NuxeoClientException e) {
226            if (NOT_FOUND_ERROR_STATUS == e.getStatus()) {
227                log.warn(String.format("Group %s not deleted because not found", name));
228            } else {
229                throw e;
230            }
231        }
232    }
233
234    public static String createDocument(String idOrPath, String type, String title, String description) {
235        Document document = new Document(title, type);
236        Map<String, Object> properties = new HashMap<>();
237        properties.put("dc:title", title);
238        if (description != null) {
239            properties.put("dc:description", description);
240        }
241        document.setProperties(properties);
242
243        if (idOrPath.startsWith("/")) {
244            document = CLIENT.repository().createDocumentByPath(idOrPath, document);
245        } else {
246            document = CLIENT.repository().createDocumentById(idOrPath, document);
247        }
248
249        String docId = document.getId();
250        String docPath = document.getPath();
251        // do we already have to delete one parent?
252        if (documentPathsToDelete.stream().noneMatch(docPath::startsWith)) {
253            documentIdsToDelete.add(docId);
254            documentPathsToDelete.add(docPath);
255        }
256        return docId;
257    }
258
259    public static void deleteDocument(String idOrPath) {
260        // TODO change that by proper deleteDocument(String)
261        if (idOrPath.startsWith("/")) {
262            // @yannis : temporary way to avoid DocumentNotFoundException in server log before NXP-19658
263            Documents documents = CLIENT.repository().query(String.format(DOCUMENT_QUERY_BY_PATH_BASE, idOrPath));
264            if (documents.size() > 0) {
265                CLIENT.repository().deleteDocument(documents.getDocument(0));
266            }
267        } else {
268            CLIENT.repository().deleteDocument(CLIENT.repository().fetchDocumentById(idOrPath));
269        }
270    }
271
272    public static void addPermission(String idOrPath, String username, String permission) {
273        Document document;
274        if (idOrPath.startsWith("/")) {
275            document = CLIENT.repository().fetchDocumentByPath(idOrPath);
276        } else {
277            document = CLIENT.repository().fetchDocumentById(idOrPath);
278        }
279
280        ACE ace = new ACE();
281        ace.setUsername(username);
282        ace.setPermission(permission);
283
284        // @yannis : temporary fix for setting permission before JAVACLIENT-90 is done
285        Calendar beginDate = Calendar.getInstance();
286        ace.setBegin(beginDate);
287        Calendar endDate = Calendar.getInstance();
288        endDate.add(Calendar.YEAR, 1);
289        ace.setEnd(endDate);
290
291        document.addPermission(ace);
292    }
293
294    public static void removePermissions(String idOrPath, String username) {
295        Document document;
296        if (idOrPath.startsWith("/")) {
297            document = CLIENT.repository().fetchDocumentByPath(idOrPath);
298        } else {
299            document = CLIENT.repository().fetchDocumentById(idOrPath);
300        }
301
302        document.removePermission(username);
303    }
304
305    /**
306     * @since 9.2
307     */
308    public static String createDirectoryEntry(String directoryName, Map<String, String> properties) {
309        Response response = CLIENT.post(NUXEO_URL + "/api/v1/directory/" + directoryName,
310                buildDirectoryEntryJSON(directoryName, properties));
311        if (!response.isSuccessful()) {
312            throw new NuxeoClientException(
313                    String.format("Unable to create entry for directory %s: %s", directoryName, properties));
314        }
315        try (ResponseBody responseBody = response.body()) {
316            JsonNode jsonNode = MAPPER.readTree(responseBody.charStream());
317            String entryId = jsonNode.get("properties").get("id").getValueAsText();
318            addDirectoryEntryToDelete(directoryName, entryId);
319            return entryId;
320        } catch (IOException e) {
321            throw new NuxeoClientException(e);
322        }
323    }
324
325    /**
326     * @since 9.2
327     */
328    public static void deleteDirectoryEntry(String directoryName, String entryId) {
329        // Work around JAVACLIENT-133 by passing an empty string
330        CLIENT.delete(NUXEO_URL + "/api/v1/directory/" + directoryName + "/" + entryId, "");
331    }
332
333    protected static String buildDirectoryEntryJSON(String directoryName, Map<String, String> properties) {
334        StringBuilder sb = new StringBuilder();
335        sb.append("{");
336        sb.append("\"entity-type\": \"directoryEntry\"").append(",\n");
337        sb.append("\"directoryName\": \"").append(directoryName).append("\",\n");
338        sb.append("\"properties\": {").append("\n");
339        sb.append(properties.keySet()
340                            .stream()
341                            .map(key -> new StringBuilder().append("\"")
342                                                           .append(key)
343                                                           .append("\": \"")
344                                                           .append(properties.get(key))
345                                                           .append("\""))
346                            .collect(Collectors.joining(",\n")));
347        sb.append("\n").append("}").append("\n");
348        sb.append("}");
349        return sb.toString();
350    }
351
352    public static void logOnServer(String level, String message) {
353        CLIENT.get(String.format("%s/restAPI/systemLog?token=dolog&level=%s&message=%s", AbstractTest.NUXEO_URL, level,
354                URIUtils.quoteURIPathComponent(message, true)));
355    }
356
357}