/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2013-2016 ForgeRock AS. */ package org.forgerock.opendj.rest2ldap.authz; import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; import static org.forgerock.opendj.rest2ldap.authz.Utils.newIllegalArgumentException; import static org.forgerock.util.Utils.joinAsString; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.forgerock.json.JsonPointer; import org.forgerock.json.JsonValue; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.schema.Schema; import org.forgerock.services.context.SecurityContext; /** * An authorization ID template used for mapping security context principals to * AuthzID templates of the form * dn:uid={uid},ou={realm},dc=example,dc=com, or * u:{uid}@{realm}.example.com. */ final class AuthzIdTemplate { private interface Impl { String formatAsAuthzId(AuthzIdTemplate t, Object[] templateVariables); } private static final Impl DN_TEMPLATE_IMPL = new Impl() { @Override public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables) { // We're not interested in matching and place-holder attribute types can be tolerated, // so we can just use the core schema. return DN.format(t.formatString, Schema.getCoreSchema(), templateVariables).toString(); } }; private static final Impl UID_TEMPLATE_IMPL = new Impl() { @Override public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables) { return String.format(Locale.ENGLISH, t.formatString, templateVariables); } }; private static final Pattern TEMPLATE_KEY_RE = Pattern.compile("\\{([^}]+)\\}"); private enum TemplateType { DN ("dn:", SecurityContext.AUTHZID_DN, DN_TEMPLATE_IMPL), UID ("u:", SecurityContext.AUTHZID_ID, UID_TEMPLATE_IMPL); private final String key; private final String securityContextId; private final Impl impl; TemplateType(final String key, final String securityContextId, final Impl impl) { this.key = key; this.securityContextId = securityContextId; this.impl = impl; } private String getSecurityContextId() { return securityContextId; } private Impl getImpl() { return impl; } private static TemplateType parseTemplateType(final String template) { for (final TemplateType type : TemplateType.values()) { if (template.startsWith(type.key)) { return type; } } throw newIllegalArgumentException( ERR_CONFIG_INVALID_AUTHZID_TEMPLATE.get(template, joinAsString(",", getSupportedStartKeys()))); } private static List getSupportedStartKeys() { final List startKeys = new ArrayList<>(); for (final TemplateType type : TemplateType.values()) { startKeys.add(type.key); } return startKeys; } private String removeTemplateKey(final String formattedString) { return formattedString.substring(key.length()).trim(); } } private final TemplateType type; private final String formatString; private final List keys = new ArrayList<>(); private final String template; /** * Create a new authorization ID template. * * @param template * Authorization ID template * @throws IllegalArgumentException * if template doesn't start with "u:" or "dn:" */ AuthzIdTemplate(final String template) { this.type = TemplateType.parseTemplateType(template); this.formatString = formatTemplate(template); this.template = template; } private String formatTemplate(final String template) { // Parse the template keys and replace them with %s for formatting. final Matcher matcher = TEMPLATE_KEY_RE.matcher(template); final StringBuffer buffer = new StringBuffer(template.length()); while (matcher.find()) { matcher.appendReplacement(buffer, "%s"); keys.add(matcher.group(1)); } matcher.appendTail(buffer); return type.removeTemplateKey(buffer.toString()); } @Override public String toString() { return template; } String getSecurityContextID() { return this.type.getSecurityContextId(); } /** * Return the template with all the variable replaced. * * @param principals * Value to use to replace the variables. * @return The template with all the variable replaced. */ String formatAsAuthzId(final JsonValue principals) { final String[] templateVariables = getPrincipalsForFormatting(principals); return type.getImpl().formatAsAuthzId(this, templateVariables); } private String[] getPrincipalsForFormatting(final JsonValue principals) { final String[] values = new String[keys.size()]; for (int i = 0; i < values.length; i++) { final String key = keys.get(i); final JsonValue value = principals.get(new JsonPointer(key)); if (value == null) { throw newIllegalArgumentException(ERR_AUTHZID_DECODER_PRINCIPAL_CANNOT_BE_DETERMINED.get(key)); } final Object object = value.getObject(); if (!isJSONPrimitive(object)) { throw newIllegalArgumentException(ERR_AUTHZID_DECODER_PRINCIPAL_INVALID_DATA_TYPE.get(key)); } values[i] = String.valueOf(object); } return values; } private boolean isJSONPrimitive(final Object value) { return value instanceof String || value instanceof Boolean || value instanceof Number; } }