001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2016 ForgeRock AS. 015 * 016 */ 017package org.opends.server.protocols.http.rest2ldap; 018 019import static org.forgerock.http.routing.RouteMatchers.newResourceApiVersionBehaviourManager; 020import static org.forgerock.http.routing.Version.version; 021import static org.forgerock.json.resource.RouteMatchers.resourceApiVersionContextFilter; 022import static org.forgerock.json.resource.http.CrestHttp.newHttpHandler; 023import static org.forgerock.opendj.ldap.schema.CoreSchema.getBooleanSyntax; 024import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax; 025import static org.forgerock.opendj.rest2ldap.Rest2Ldap.*; 026import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.CREATE_ONLY; 027import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_ONLY; 028import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE; 029import static org.forgerock.util.Options.defaultOptions; 030import static org.opends.messages.ConfigMessages.ERR_BAD_ADMIN_API_RESOURCE_VERSION; 031 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037 038import org.forgerock.http.Handler; 039import org.forgerock.http.HttpApplication; 040import org.forgerock.http.HttpApplicationException; 041import org.forgerock.http.io.Buffer; 042import org.forgerock.http.routing.ResourceApiVersionBehaviourManager; 043import org.forgerock.http.routing.Version; 044import org.forgerock.json.JsonPointer; 045import org.forgerock.json.resource.BadRequestException; 046import org.forgerock.json.resource.FilterChain; 047import org.forgerock.json.resource.Request; 048import org.forgerock.json.resource.RequestHandler; 049import org.forgerock.json.resource.ResourceException; 050import org.forgerock.json.resource.Router; 051import org.forgerock.opendj.config.AbstractManagedObjectDefinition; 052import org.forgerock.opendj.config.AggregationPropertyDefinition; 053import org.forgerock.opendj.config.DefaultBehaviorProvider; 054import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider; 055import org.forgerock.opendj.config.InstantiableRelationDefinition; 056import org.forgerock.opendj.config.LDAPProfile; 057import org.forgerock.opendj.config.ManagedObjectDefinition; 058import org.forgerock.opendj.config.ManagedObjectOption; 059import org.forgerock.opendj.config.PropertyDefinition; 060import org.forgerock.opendj.config.PropertyOption; 061import org.forgerock.opendj.config.RelationDefinition; 062import org.forgerock.opendj.config.RelationOption; 063import org.forgerock.opendj.config.SingletonRelationDefinition; 064import org.forgerock.opendj.config.TopCfgDefn; 065import org.forgerock.opendj.ldap.AttributeDescription; 066import org.forgerock.opendj.ldap.DN; 067import org.forgerock.opendj.ldap.Functions; 068import org.forgerock.opendj.ldap.schema.Syntax; 069import org.forgerock.opendj.rest2ldap.AbstractRequestHandler; 070import org.forgerock.opendj.rest2ldap.ReferencePropertyMapper; 071import org.forgerock.opendj.rest2ldap.Resource; 072import org.forgerock.opendj.rest2ldap.Rest2Ldap; 073import org.forgerock.opendj.rest2ldap.SimplePropertyMapper; 074import org.forgerock.opendj.rest2ldap.SubResourceCollection; 075import org.forgerock.opendj.rest2ldap.SubResourceSingleton; 076import org.forgerock.opendj.server.config.meta.GlobalCfgDefn; 077import org.forgerock.opendj.server.config.meta.RootCfgDefn; 078import org.forgerock.opendj.server.config.server.AdminEndpointCfg; 079import org.forgerock.services.context.Context; 080import org.forgerock.util.Factory; 081import org.forgerock.util.Function; 082import org.forgerock.util.promise.NeverThrowsException; 083import org.forgerock.util.promise.Promise; 084import org.opends.server.api.HttpEndpoint; 085import org.opends.server.core.ServerContext; 086import org.opends.server.types.InitializationException; 087 088/** 089 * An HTTP endpoint providing access to the server's monitoring backend (cn=monitor) and its configuration (cn=config). 090 */ 091public final class AdminEndpoint extends HttpEndpoint<AdminEndpointCfg> 092{ 093 private static final Version ADMIN_API_VERSION = version("1.0"); 094 private static final String TYPE_PROPERTY = "_schema"; 095 private static final String ADMIN_API = "admin-api"; 096 private static final String MONITOR = "monitor"; 097 private static final String CONFIG = "config"; 098 099 /** 100 * Create a new AdminEndpoint with the supplied configuration. 101 * 102 * @param configuration 103 * Configuration to use for the {@link HttpApplication} 104 * @param serverContext 105 * Server of this LDAP server 106 */ 107 public AdminEndpoint(AdminEndpointCfg configuration, ServerContext serverContext) 108 { 109 super(configuration, serverContext); 110 } 111 112 @Override 113 public HttpApplication newHttpApplication() throws InitializationException 114 { 115 return new AdminHttpApplication(); 116 } 117 118 /** 119 * Specialized {@link HttpApplication} using internal connections to this local LDAP server. 120 */ 121 private final class AdminHttpApplication implements HttpApplication 122 { 123 private LDAPProfile ldapProfile = LDAPProfile.getInstance(); 124 125 @Override 126 public Handler start() throws HttpApplicationException 127 { 128 final Map<String, Resource> resources = new HashMap<>(); 129 130 // Define the entry point to the admin API. 131 resources.put(ADMIN_API, resource(ADMIN_API).subResources(singletonOf(MONITOR).urlTemplate(MONITOR) 132 .dnTemplate("cn=monitor") 133 .isReadOnly(true), 134 singletonOf(CONFIG).urlTemplate(CONFIG) 135 .dnTemplate("cn=config"))); 136 137 // Define the monitoring endpoint. 138 resources.put(MONITOR, resource(MONITOR).includeAllUserAttributesByDefault(true) 139 .excludedDefaultUserAttributes("objectClass", "cn") 140 .objectClass ("ds-monitor-entry") 141 .property("_id", simple("cn")) 142 .subResource(collectionOf(MONITOR).useClientDnNaming("cn"))); 143 144 // Build the configuration endpoint using the configuration framework. 145 final TopCfgDefn topCfgDefn = TopCfgDefn.getInstance(); 146 final RootCfgDefn rootCfgDefn = RootCfgDefn.getInstance(); 147 final GlobalCfgDefn globalCfgDefn = GlobalCfgDefn.getInstance(); 148 149 // The configuration framework exposes the root and global configuration as separate resources, but it would be 150 // nice if we exposed them as a single resource. 151 final Resource config = resource(CONFIG); 152 configureResourceProperties(globalCfgDefn, config); 153 configureResourceSubResources(rootCfgDefn, config, true); 154 resources.put(CONFIG, config); 155 156 resources.put(topCfgDefn.getName(), buildResource(topCfgDefn)); 157 for (final AbstractManagedObjectDefinition<?, ?> mod : topCfgDefn.getAllChildren()) 158 { 159 if (!mod.hasOption(ManagedObjectOption.HIDDEN) && mod != globalCfgDefn && mod != rootCfgDefn) 160 { 161 resources.put(mod.getName(), buildResource(mod)); 162 } 163 } 164 165 // Now that all resources are defined, perform a second pass processing all relation definitions in order to 166 // identity which attributes should be used for the "_id" property. 167 for (final AbstractManagedObjectDefinition<?, ?> mod : topCfgDefn.getAllChildren()) 168 { 169 for (final RelationDefinition<?, ?> rd : mod.getRelationDefinitions()) 170 { 171 if (rd instanceof InstantiableRelationDefinition) 172 { 173 final InstantiableRelationDefinition<?, ?> ird = (InstantiableRelationDefinition) rd; 174 final AbstractManagedObjectDefinition<?, ?> d = rd.getChildDefinition(); 175 final String rdnType = ldapProfile.getRelationChildRDNType(ird); 176 resources.get(d.getName()).property("_id", simple(rdnType).isRequired(true).writability(CREATE_ONLY)); 177 } 178 } 179 } 180 181 final Rest2Ldap rest2Ldap = rest2Ldap(defaultOptions(), resources.values()); 182 final RequestHandler handler = rest2Ldap.newRequestHandlerFor(ADMIN_API); 183 final Router versionRouter = new Router(); 184 versionRouter.addRoute(ADMIN_API_VERSION, handler); 185 versionRouter.setDefaultRoute(new AbstractRequestHandler() 186 { 187 @Override 188 protected <V> Promise<V, ResourceException> handleRequest(final Context context, final Request request) 189 { 190 final String message = ERR_BAD_ADMIN_API_RESOURCE_VERSION.get(request.getResourceVersion(), ADMIN_API_VERSION) 191 .toString(); 192 return new BadRequestException(message).asPromise(); 193 } 194 }); 195 196 // FIXME: Disable the warning header for now due to CREST-389 / CREST-390. 197 final ResourceApiVersionBehaviourManager behaviourManager = newResourceApiVersionBehaviourManager(); 198 behaviourManager.setWarningEnabled(false); 199 return newHttpHandler(new FilterChain(versionRouter, resourceApiVersionContextFilter(behaviourManager))); 200 } 201 202 private Resource buildResource(final AbstractManagedObjectDefinition<?, ?> mod) 203 { 204 final Resource resource = resource(mod.getName()); 205 configureResourceProperties(mod, resource); 206 configureResourceSubResources(mod, resource, false); 207 return resource; 208 } 209 210 private void configureResourceSubResources(final AbstractManagedObjectDefinition<?, ?> mod, final Resource resource, 211 final boolean removeCnEqualsConfig) 212 { 213 for (final RelationDefinition<?, ?> rd : mod.getRelationDefinitions()) 214 { 215 if (rd.hasOption(RelationOption.HIDDEN)) 216 { 217 continue; 218 } 219 220 if (rd instanceof InstantiableRelationDefinition) 221 { 222 final InstantiableRelationDefinition<?, ?> ird = (InstantiableRelationDefinition) rd; 223 final AbstractManagedObjectDefinition<?, ?> d = rd.getChildDefinition(); 224 final SubResourceCollection collection = collectionOf(d.getName()) 225 .useClientDnNaming(ldapProfile.getRelationChildRDNType(ird)) 226 .urlTemplate(ird.getPluralName()) 227 .dnTemplate(getRelationRdnSequence(rd, removeCnEqualsConfig)) 228 .glueObjectClasses(ldapProfile.getRelationObjectClasses(rd).toArray(new String[0])); 229 resource.subResource(collection); 230 } 231 else if (rd instanceof SingletonRelationDefinition) 232 { 233 if (mod == RootCfgDefn.getInstance() && rd.getChildDefinition() == GlobalCfgDefn.getInstance()) 234 { 235 // Special case: ignore the root -> global configuration relation because these two resources are merged 236 // into a single resource within the REST API. 237 continue; 238 } 239 final SubResourceSingleton singleton = singletonOf(rd.getChildDefinition().getName()) 240 .urlTemplate(rd.getName()) 241 .dnTemplate(getRelationRdnSequence(rd, removeCnEqualsConfig)); 242 resource.subResource(singleton); 243 } 244 // Optional/set NYI 245 } 246 } 247 248 private String getRelationRdnSequence(final RelationDefinition<?, ?> rd, final boolean removeCnEqualsConfig) 249 { 250 final String rdnSequence = ldapProfile.getRelationRDNSequence(rd); 251 if (removeCnEqualsConfig) 252 { 253 final DN dn = DN.valueOf(rdnSequence); 254 return dn.localName(dn.size() - 1).toString(); 255 } 256 return rdnSequence; 257 } 258 259 private void configureResourceProperties(final AbstractManagedObjectDefinition<?, ?> mod, final Resource resource) 260 { 261 resource.isAbstract(!(mod instanceof ManagedObjectDefinition)); 262 if (mod.getParent() != null) 263 { 264 resource.superType(mod.getParent().getName()); 265 } 266 267 final String objectClass = ldapProfile.getObjectClass(mod); 268 if (objectClass != null) 269 { 270 resource.objectClass(objectClass); 271 } 272 273 resource.resourceTypeProperty(new JsonPointer(TYPE_PROPERTY)); 274 resource.property(TYPE_PROPERTY, resourceType()); 275 resource.property("_rev", simple("etag").writability(READ_ONLY)); 276 277 for (final PropertyDefinition<?> pd : mod.getPropertyDefinitions()) 278 { 279 if (pd.hasOption(PropertyOption.HIDDEN)) 280 { 281 continue; 282 } 283 284 final String attributeName = ldapProfile.getAttributeName(mod, pd); 285 if (pd instanceof AggregationPropertyDefinition) 286 { 287 final AggregationPropertyDefinition apd = (AggregationPropertyDefinition) pd; 288 final String relationChildRdnType = ldapProfile.getRelationChildRDNType(apd.getRelationDefinition()); 289 final SimplePropertyMapper referencePropertyMapper = simple(relationChildRdnType).isRequired(true); 290 final DN baseDn = apd.getParentPath().toDN() 291 .child(ldapProfile.getRelationRDNSequence(apd.getRelationDefinition())); 292 final ReferencePropertyMapper mapper = reference(attributeName, 293 baseDn.toString(), 294 relationChildRdnType, 295 referencePropertyMapper); 296 resource.property(pd.getName(), mapper); 297 } 298 else 299 { 300 final SimplePropertyMapper mapper = simple(attributeName) 301 .isRequired(pd.hasOption(PropertyOption.MANDATORY)) 302 .writability(pd.hasOption(PropertyOption.READ_ONLY) ? CREATE_ONLY : READ_WRITE) 303 .isMultiValued(pd.hasOption(PropertyOption.MULTI_VALUED)); 304 305 // Define the default value as well if possible. 306 final DefaultBehaviorProvider<?> dbp = pd.getDefaultBehaviorProvider(); 307 if (dbp instanceof DefinedDefaultBehaviorProvider) 308 { 309 final DefinedDefaultBehaviorProvider<?> ddbp = (DefinedDefaultBehaviorProvider) dbp; 310 final Collection<String> defaultValues = ddbp.getDefaultValues(); 311 final List<Object> decodedDefaultValues = new ArrayList<>(defaultValues.size()); 312 final Function<String, ?, NeverThrowsException> converter = getConverter(attributeName); 313 for (final String defaultValue : defaultValues) 314 { 315 decodedDefaultValues.add(converter.apply(defaultValue)); 316 } 317 mapper.defaultJsonValues(decodedDefaultValues); 318 } 319 resource.property(pd.getName(), mapper); 320 } 321 } 322 } 323 324 private Function<String, ?, NeverThrowsException> getConverter(final String attributeName) 325 { 326 final AttributeDescription attributeDescription = AttributeDescription.valueOf(attributeName); 327 final Syntax syntax = attributeDescription.getAttributeType().getSyntax(); 328 if (syntax.equals(getBooleanSyntax())) 329 { 330 return Functions.stringToBoolean(); 331 } 332 else if (syntax.equals(getIntegerSyntax())) 333 { 334 return Functions.stringToLong(); 335 } 336 else 337 { 338 return Functions.identityFunction(); 339 } 340 } 341 342 @Override 343 public void stop() 344 { 345 // Nothing to do 346 } 347 348 @Override 349 public Factory<Buffer> getBufferFactory() 350 { 351 return null; 352 } 353 } 354}