001/*
002 * (C) Copyright 2006-2017 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 *     Florent Guillaume
018 *     Mincong Huang
019 */
020package org.nuxeo.ecm.core.opencmis.impl.server;
021
022import static org.apache.chemistry.opencmis.commons.BasicPermissions.ALL;
023import static org.apache.chemistry.opencmis.commons.BasicPermissions.READ;
024import static org.apache.chemistry.opencmis.commons.BasicPermissions.WRITE;
025import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_OBJECT;
026import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_POLICY_POLICY;
027import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_FOLDER;
028import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_ADD_TO_FOLDER_OBJECT;
029import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_APPLY_ACL_OBJECT;
030import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT;
031import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKIN_DOCUMENT;
032import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CHECKOUT_DOCUMENT;
033import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER;
034import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_FOLDER_FOLDER;
035import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_SOURCE;
036import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_CREATE_RELATIONSHIP_TARGET;
037import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT;
038import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_OBJECT;
039import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_DELETE_TREE_FOLDER;
040import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ACL_OBJECT;
041import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES;
042import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_APPLIED_POLICIES_OBJECT;
043import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_CHILDREN_FOLDER;
044import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_DESCENDENTS_FOLDER;
045import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT;
046import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT;
047import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PARENTS_FOLDER;
048import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_GET_PROPERTIES_OBJECT;
049import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_OBJECT;
050import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_SOURCE;
051import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_MOVE_TARGET;
052import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_FOLDER;
053import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_FROM_FOLDER_OBJECT;
054import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_OBJECT;
055import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_REMOVE_POLICY_POLICY;
056import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_SET_CONTENT_DOCUMENT;
057import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT;
058import static org.apache.chemistry.opencmis.commons.data.PermissionMapping.CAN_VIEW_CONTENT_OBJECT;
059
060import java.lang.reflect.Field;
061import java.util.ArrayList;
062import java.util.Arrays;
063import java.util.Collections;
064import java.util.HashMap;
065import java.util.HashSet;
066import java.util.LinkedList;
067import java.util.List;
068import java.util.Map;
069import java.util.Map.Entry;
070import java.util.Set;
071
072import javax.servlet.http.HttpServletRequest;
073
074import org.apache.chemistry.opencmis.commons.data.ExtensionFeature;
075import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
076import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
077import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition;
078import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
079import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
080import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
081import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl;
082import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
083import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
084import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
085import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
086import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
087import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
088import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions;
089import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
090import org.apache.chemistry.opencmis.commons.impl.dataobjects.CreatablePropertyTypesImpl;
091import org.apache.chemistry.opencmis.commons.impl.dataobjects.NewTypeSettableAttributesImpl;
092import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
093import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
094import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
095import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
096import org.apache.chemistry.opencmis.commons.server.CallContext;
097
098import org.nuxeo.common.Environment;
099import org.nuxeo.ecm.core.api.security.PermissionProvider;
100import org.nuxeo.ecm.core.api.security.SecurityConstants;
101import org.nuxeo.ecm.core.opencmis.impl.util.TypeManagerImpl;
102import org.nuxeo.ecm.core.schema.DocumentType;
103import org.nuxeo.ecm.core.schema.SchemaManager;
104import org.nuxeo.ecm.core.schema.types.CompositeType;
105import org.nuxeo.ecm.core.security.DefaultPermissionProvider;
106import org.nuxeo.ecm.core.security.PermissionVisibilityDescriptor;
107import org.nuxeo.runtime.api.Framework;
108
109/**
110 * Information about a Nuxeo repository.
111 */
112public class NuxeoRepository {
113
114    /**
115     * @deprecated Since 7.10. Use {@link Environment#DISTRIBUTION_VERSION}
116     */
117    @Deprecated
118    public static final String NUXEO_VERSION_PROP = Environment.DISTRIBUTION_VERSION;
119
120    public static final String NUXEO_URL_PROP = "nuxeo.url";
121
122    public static final String SUPPORTS_JOINS_PROP = "org.nuxeo.cmis.joins";
123
124    public static final String SUPPORTS_PROXIES_PROP = "org.nuxeo.cmis.proxies";
125
126    public static final String ELASTICSEARCH_PROP = "org.nuxeo.cmis.elasticsearch";
127
128    /**
129     * Key of the configuration property {@code "org.nuxeo.cmis.relaxSpec"}, default to {@code false}. Setting this
130     * property to {@code true} allows users to relax the CMIS specification 1.1 and use customized CMISQL. Please be
131     * aware the risk of doing so. It will potentially cause query parsing error.
132     * <p>
133     * The relax mode does not follow the CMIS specification 1.1, section 2.1.14.2.4.4, where at most one
134     * {@code CONTAINS()} function MUST be included in a single query statement. Currently, such mode only works for
135     * CMIS query having no {@code JOIN} predicate.
136     *
137     * @see https://jira.nuxeo.com/browse/NXP-19858
138     */
139    public static final String RELAX_CMIS_SPEC = "org.nuxeo.cmis.relaxSpec";
140
141    private static final String NUXEO_CONTEXT_PATH_PROP = "org.nuxeo.ecm.contextPath";
142
143    private static final String NUXEO_CONTEXT_PATH_DEFAULT = "/nuxeo";
144
145    private static final String X_FORWARDED_HOST = "x-forwarded-host";
146
147    private static final String NUXEO_VH_HEADER = "nuxeo-virtual-host";
148
149    private static final String VH_PARAM = "nuxeo.virtual.host";
150
151    public static final String NUXEO_READ_REMOVE = "ReadRemove";
152
153    protected final String repositoryId;
154
155    protected final String rootFolderId;
156
157    protected boolean supportsJoins;
158
159    protected boolean supportsProxies = true;
160
161    protected boolean useElasticsearch;
162
163    protected Map<CmisVersion, TypeManagerImpl> typeManagerByCmisVersion = new HashMap<>();
164
165    public NuxeoRepository(String repositoryId, String rootFolderId) {
166        this.repositoryId = repositoryId;
167        this.rootFolderId = rootFolderId;
168        if (Framework.isBooleanPropertyTrue(SUPPORTS_JOINS_PROP)) {
169            setSupportsJoins(true);
170        }
171        if (Framework.isBooleanPropertyFalse(SUPPORTS_PROXIES_PROP)) {
172            setSupportsProxies(false);
173        }
174        if (Framework.isBooleanPropertyTrue(ELASTICSEARCH_PROP)) {
175            setUseElasticsearch(true);
176        }
177    }
178
179    public void setSupportsJoins(boolean supportsJoins) {
180        this.supportsJoins = supportsJoins;
181    }
182
183    public boolean supportsJoins() {
184        return supportsJoins;
185    }
186
187    public void setSupportsProxies(boolean supportsProxies) {
188        this.supportsProxies = supportsProxies;
189    }
190
191    public boolean supportsProxies() {
192        return supportsProxies;
193    }
194
195    public void setUseElasticsearch(boolean useElasticsearch) {
196        this.useElasticsearch = useElasticsearch;
197    }
198
199    public boolean useElasticsearch() {
200        return useElasticsearch;
201    }
202
203    public String getId() {
204        return repositoryId;
205    }
206
207    // no need to have it synchronized
208    public TypeManagerImpl getTypeManager(CmisVersion cmisVersion) {
209        TypeManagerImpl typeManager = typeManagerByCmisVersion.get(cmisVersion);
210        if (typeManager == null) {
211            typeManager = initializeTypes(cmisVersion);
212            typeManagerByCmisVersion.put(cmisVersion, typeManager);
213        }
214        return typeManager;
215    }
216
217    protected TypeManagerImpl initializeTypes(CmisVersion cmisVersion) {
218        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
219        // scan the types to find super/inherited relationships
220        Map<String, List<String>> typesChildren = new HashMap<>();
221        for (DocumentType dt : schemaManager.getDocumentTypes()) {
222            org.nuxeo.ecm.core.schema.types.Type st = dt.getSuperType();
223            if (st == null) {
224                continue;
225            }
226            String name = st.getName();
227            List<String> siblings = typesChildren.get(name);
228            if (siblings == null) {
229                siblings = new LinkedList<>();
230                typesChildren.put(name, siblings);
231            }
232            siblings.add(dt.getName());
233        }
234        // convert the transitive closure for Folder and Document subtypes
235        Set<String> done = new HashSet<>();
236        TypeManagerImpl typeManager = new TypeManagerImpl();
237        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_DOCUMENT, schemaManager, cmisVersion));
238        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_FOLDER, schemaManager, cmisVersion));
239        typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_RELATIONSHIP, schemaManager, cmisVersion));
240        if (cmisVersion != CmisVersion.CMIS_1_0) {
241            typeManager.addTypeDefinition(NuxeoTypeHelper.constructCmisBase(BaseTypeId.CMIS_SECONDARY, schemaManager, cmisVersion));
242        }
243        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_DOCUMENT, typesChildren, done, schemaManager, cmisVersion);
244        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_FOLDER, typesChildren, done, schemaManager, cmisVersion);
245        addTypesRecursively(typeManager, NuxeoTypeHelper.NUXEO_RELATION, typesChildren, done, schemaManager, cmisVersion);
246        if (cmisVersion != CmisVersion.CMIS_1_0) {
247            addSecondaryTypes(typeManager, schemaManager, cmisVersion);
248        }
249        return typeManager;
250    }
251
252    protected void addTypesRecursively(TypeManagerImpl typeManager, String name,
253            Map<String, List<String>> typesChildren, Set<String> done, SchemaManager schemaManager,
254            CmisVersion cmisVersion) {
255        if (done.contains(name)) {
256            return;
257        }
258        done.add(name);
259        DocumentType dt = schemaManager.getDocumentType(name);
260        String parentTypeId = NuxeoTypeHelper.getParentTypeId(dt);
261        if (parentTypeId != null) {
262            TypeDefinitionContainer parentType = typeManager.getTypeById(parentTypeId);
263            if (parentType == null) {
264                // if parent was ignored, reparent under cmis:document
265                parentTypeId = BaseTypeId.CMIS_DOCUMENT.value();
266            } else {
267                if (parentType.getTypeDefinition().getBaseTypeId() != BaseTypeId.CMIS_FOLDER && dt.isFolder()) {
268                    // reparent Folderish but child of Document under
269                    // cmis:folder
270                    parentTypeId = BaseTypeId.CMIS_FOLDER.value();
271                }
272            }
273            typeManager.addTypeDefinition(NuxeoTypeHelper.constructDocumentType(dt, parentTypeId, cmisVersion));
274        }
275        // recurse in children
276        List<String> children = typesChildren.get(name);
277        if (children == null) {
278            return;
279        }
280        for (String sub : children) {
281            addTypesRecursively(typeManager, sub, typesChildren, done, schemaManager, cmisVersion);
282        }
283    }
284
285    protected void addSecondaryTypes(TypeManagerImpl typeManager, SchemaManager schemaManager,
286            CmisVersion cmisVersion) {
287        for (CompositeType type : schemaManager.getFacets()) {
288            typeManager.addTypeDefinition(NuxeoTypeHelper.constructSecondaryType(type, cmisVersion), false);
289        }
290    }
291
292    public String getRootFolderId() {
293        return rootFolderId;
294    }
295
296    public RepositoryInfo getRepositoryInfo(String latestChangeLogToken, CallContext callContext) {
297        CmisVersion cmisVersion = callContext.getCmisVersion();
298
299        RepositoryInfoImpl repositoryInfo = new RepositoryInfoImpl();
300        repositoryInfo.setId(repositoryId);
301        repositoryInfo.setName("Nuxeo Repository " + repositoryId);
302        repositoryInfo.setDescription("Nuxeo Repository " + repositoryId);
303        repositoryInfo.setCmisVersionSupported(cmisVersion.value());
304        repositoryInfo.setPrincipalAnonymous("Guest"); // TODO
305        repositoryInfo.setPrincipalAnyone(SecurityConstants.EVERYONE);
306        repositoryInfo.setThinClientUri(getBaseURL(callContext));
307        repositoryInfo.setChangesIncomplete(Boolean.FALSE);
308        repositoryInfo.setChangesOnType(Arrays.asList(BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER));
309        repositoryInfo.setLatestChangeLogToken(latestChangeLogToken);
310        repositoryInfo.setVendorName("Nuxeo");
311        repositoryInfo.setProductName("Nuxeo OpenCMIS Connector");
312        String version = Framework.getProperty(Environment.DISTRIBUTION_VERSION, "5.5 dev");
313        repositoryInfo.setProductVersion(version);
314        repositoryInfo.setRootFolder(rootFolderId);
315        repositoryInfo.setExtensionFeature(Collections.<ExtensionFeature> emptyList());
316
317        // capabilities
318
319        RepositoryCapabilitiesImpl caps = new RepositoryCapabilitiesImpl();
320        caps.setAllVersionsSearchable(Boolean.TRUE);
321        caps.setCapabilityAcl(CapabilityAcl.MANAGE);
322        caps.setCapabilityChanges(CapabilityChanges.OBJECTIDSONLY);
323        caps.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.PWCONLY);
324        caps.setCapabilityJoin(supportsJoins ? CapabilityJoin.INNERANDOUTER : CapabilityJoin.NONE);
325        caps.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED);
326        caps.setCapabilityRendition(CapabilityRenditions.READ);
327        caps.setIsPwcSearchable(Boolean.TRUE);
328        caps.setIsPwcUpdatable(Boolean.TRUE);
329        caps.setSupportsGetDescendants(Boolean.TRUE);
330        caps.setSupportsGetFolderTree(Boolean.TRUE);
331        caps.setSupportsMultifiling(Boolean.FALSE);
332        caps.setSupportsUnfiling(Boolean.FALSE);
333        caps.setSupportsVersionSpecificFiling(Boolean.FALSE);
334        caps.setNewTypeSettableAttributes(new NewTypeSettableAttributesImpl());
335        caps.setCreatablePropertyTypes(new CreatablePropertyTypesImpl());
336        repositoryInfo.setCapabilities(caps);
337
338        // ACL capabilities
339
340        AclCapabilitiesDataImpl aclCaps = new AclCapabilitiesDataImpl();
341        aclCaps.setAclPropagation(AclPropagation.PROPAGATE);
342        aclCaps.setSupportedPermissions(SupportedPermissions.REPOSITORY);
343
344        List<PermissionDefinition> permDefs = new ArrayList<>();
345        addPermissionDefinitions(permDefs);
346        aclCaps.setPermissionDefinitionData(permDefs);
347
348        Map<String, PermissionMapping> permMap = new HashMap<>();
349        addPermissionMapping(permMap, CAN_GET_DESCENDENTS_FOLDER, READ);
350        addPermissionMapping(permMap, CAN_GET_CHILDREN_FOLDER, READ);
351        addPermissionMapping(permMap, CAN_GET_PARENTS_FOLDER, READ);
352        addPermissionMapping(permMap, CAN_GET_FOLDER_PARENT_OBJECT, READ);
353        addPermissionMapping(permMap, CAN_CREATE_DOCUMENT_FOLDER, WRITE);
354        addPermissionMapping(permMap, CAN_CREATE_FOLDER_FOLDER, WRITE);
355        // no CAN_CREATE_POLICY_FOLDER due to spec bug
356        addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_SOURCE, READ);
357        addPermissionMapping(permMap, CAN_CREATE_RELATIONSHIP_TARGET, READ);
358        addPermissionMapping(permMap, CAN_GET_PROPERTIES_OBJECT, READ);
359        addPermissionMapping(permMap, CAN_VIEW_CONTENT_OBJECT, READ);
360        addPermissionMapping(permMap, CAN_UPDATE_PROPERTIES_OBJECT, WRITE);
361        addPermissionMapping(permMap, CAN_MOVE_OBJECT, WRITE);
362        addPermissionMapping(permMap, CAN_MOVE_TARGET, WRITE);
363        addPermissionMapping(permMap, CAN_MOVE_SOURCE, WRITE);
364        addPermissionMapping(permMap, CAN_DELETE_OBJECT, WRITE);
365        addPermissionMapping(permMap, CAN_DELETE_TREE_FOLDER, WRITE);
366        addPermissionMapping(permMap, CAN_SET_CONTENT_DOCUMENT, WRITE);
367        addPermissionMapping(permMap, CAN_DELETE_CONTENT_DOCUMENT, WRITE);
368        addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_OBJECT, WRITE);
369        addPermissionMapping(permMap, CAN_ADD_TO_FOLDER_FOLDER, WRITE);
370        addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_OBJECT, WRITE);
371        addPermissionMapping(permMap, CAN_REMOVE_FROM_FOLDER_FOLDER, WRITE);
372        addPermissionMapping(permMap, CAN_CHECKOUT_DOCUMENT, WRITE);
373        addPermissionMapping(permMap, CAN_CANCEL_CHECKOUT_DOCUMENT, WRITE);
374        addPermissionMapping(permMap, CAN_CHECKIN_DOCUMENT, WRITE);
375        addPermissionMapping(permMap, CAN_GET_ALL_VERSIONS_VERSION_SERIES, READ);
376        addPermissionMapping(permMap, CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, READ);
377        addPermissionMapping(permMap, CAN_ADD_POLICY_OBJECT, WRITE);
378        addPermissionMapping(permMap, CAN_ADD_POLICY_POLICY, WRITE);
379        addPermissionMapping(permMap, CAN_REMOVE_POLICY_OBJECT, WRITE);
380        addPermissionMapping(permMap, CAN_REMOVE_POLICY_POLICY, WRITE);
381        addPermissionMapping(permMap, CAN_GET_APPLIED_POLICIES_OBJECT, READ);
382        addPermissionMapping(permMap, CAN_GET_ACL_OBJECT, READ);
383        addPermissionMapping(permMap, CAN_APPLY_ACL_OBJECT, ALL);
384        aclCaps.setPermissionMappingData(permMap);
385
386        repositoryInfo.setAclCapabilities(aclCaps);
387
388        return repositoryInfo;
389    }
390
391    @SuppressWarnings("unchecked")
392    protected static void addPermissionDefinitions(List<PermissionDefinition> permDefs) {
393        addPermissionDefinition(permDefs, READ, "Read"); // = Nuxeo Read
394        addPermissionDefinition(permDefs, WRITE, "Write"); // = Nuxeo ReadWrite
395        addPermissionDefinition(permDefs, ALL, "All"); // = Nuxeo Everything
396        addPermissionDefinition(permDefs, NUXEO_READ_REMOVE, "Remove");
397
398        Set<String> done = new HashSet<>();
399        done.add(SecurityConstants.READ);
400        done.add(SecurityConstants.READ_WRITE);
401        done.add(SecurityConstants.EVERYTHING);
402        done.add(NUXEO_READ_REMOVE);
403
404        /*
405         * Add Nuxeo-specific permissions registered through the permissionsVisibility extension point.
406         */
407
408        DefaultPermissionProvider permissionProvider = (DefaultPermissionProvider) Framework.getService(PermissionProvider.class);
409        permissionProvider.getUserVisiblePermissionDescriptors(); // init var
410        Map<String, PermissionVisibilityDescriptor> map;
411        try {
412            Field f;
413            f = DefaultPermissionProvider.class.getDeclaredField("mergedPermissionsVisibility");
414            f.setAccessible(true);
415            map = (Map<String, PermissionVisibilityDescriptor>) f.get(permissionProvider);
416        } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
417            throw new RuntimeException(e);
418        }
419        // iterate for all types regisited, not just the default ""
420        for (Entry<String, PermissionVisibilityDescriptor> en : map.entrySet()) {
421            for (String permission : en.getValue().getSortedItems()) {
422                if (!done.add(permission)) {
423                    continue;
424                }
425                addPermissionDefinition(permDefs, permission, permission);
426            }
427        }
428    }
429
430    protected static void addPermissionDefinition(List<PermissionDefinition> permDefs, String permission,
431            String description) {
432        PermissionDefinitionDataImpl pd = new PermissionDefinitionDataImpl();
433        pd.setId(permission);
434        pd.setDescription(description);
435        permDefs.add(pd);
436    }
437
438    protected static void addPermissionMapping(Map<String, PermissionMapping> permMap, String key, String permission) {
439        PermissionMappingDataImpl pm = new PermissionMappingDataImpl();
440        pm.setKey(key);
441        pm.setPermissions(Collections.singletonList(permission));
442        permMap.put(key, pm);
443    }
444
445    /** Returns the server base URL (including context). */
446    private static String getBaseURL(CallContext callContext) {
447        HttpServletRequest request = (HttpServletRequest) callContext.get(CallContext.HTTP_SERVLET_REQUEST);
448        if (request != null) {
449            String baseURL = getServerURL(request);
450            String contextPath = request.getContextPath();
451            if (contextPath == null) {
452                contextPath = Framework.getProperty(NUXEO_CONTEXT_PATH_PROP, NUXEO_CONTEXT_PATH_DEFAULT);
453            }
454            // add context path
455            return baseURL + contextPath + '/';
456        } else {
457            return Framework.getProperty(NUXEO_URL_PROP);
458        }
459    }
460
461    /**
462     * Returns the server URL according to virtual hosting headers (without trailing slash).
463     */
464    private static String getServerURL(HttpServletRequest request) {
465        String url = null;
466        // Detect Nuxeo specific header for VH
467        String nuxeoVH = request.getHeader(NUXEO_VH_HEADER);
468        if (nuxeoVH == null) {
469            nuxeoVH = Framework.getProperty(VH_PARAM);
470        }
471        if (nuxeoVH != null && nuxeoVH.startsWith("http")) {
472            url = nuxeoVH;
473        } else {
474            // default values
475            String scheme = request.getScheme();
476            String serverName = request.getServerName();
477            int serverPort = request.getServerPort();
478            // Detect virtual hosting based in standard header
479            String forwardedHost = request.getHeader(X_FORWARDED_HOST);
480            if (forwardedHost != null) {
481                if (forwardedHost.contains(":")) {
482                    String[] split = forwardedHost.split(":");
483                    serverName = split[0];
484                    serverPort = Integer.parseInt(split[1]);
485                } else {
486                    serverName = forwardedHost;
487                    serverPort = 80; // fallback
488                }
489            }
490            url = buildURL(scheme, serverName, serverPort);
491        }
492        // strip trailing slash
493        if (url.endsWith("/")) {
494            url = url.substring(0, url.length() - 1);
495        }
496        return url;
497    }
498
499    /** Builds an URL (without trailing slash). */
500    private static String buildURL(String scheme, String serverName, int serverPort) {
501        StringBuilder sb = new StringBuilder();
502        sb.append(scheme);
503        sb.append("://");
504        sb.append(serverName);
505        if (serverPort != 0) {
506            if ("http".equals(scheme) && serverPort != 80 || "https".equals(scheme) && serverPort != 443) {
507                sb.append(':');
508                sb.append(serverPort);
509            }
510        }
511        return sb.toString();
512    }
513
514}