From c8585baebc9fc35ed12a3321acf47730c967b5d3 Mon Sep 17 00:00:00 2001
From: Gaetan Boismal <gaetan.boismal@forgerock.com>
Date: Tue, 24 May 2016 15:45:03 +0000
Subject: [PATCH] OPENDJ-2880 Rest2Ldap as an OAuth2 Resource Server

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java |  152 +++++++++++++++++++++++++++++---------------------
 1 files changed, 87 insertions(+), 65 deletions(-)

diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java
index 6040d0a..589bb5b 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java
@@ -15,15 +15,19 @@
  */
 package org.forgerock.opendj.rest2ldap.authz;
 
+import static org.forgerock.util.Utils.joinAsString;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 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
@@ -32,56 +36,77 @@
  * <code>u:{uid}@{realm}.example.com</code>.
  */
 final class AuthzIdTemplate {
-    private static interface Impl {
-        String formatAsAuthzId(AuthzIdTemplate t, Object[] templateVariables, Schema schema);
+
+    private interface Impl {
+        String formatAsAuthzId(AuthzIdTemplate t, Object[] templateVariables);
     }
 
-    private static final Impl DN_IMPL = new Impl() {
-
-        @Override
-        public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
-                final Schema schema) {
-            final String authzId = String.format(Locale.ENGLISH, t.formatString, templateVariables);
-            try {
-                // Validate the DN.
-                DN.valueOf(authzId.substring(3), schema);
-            } catch (final IllegalArgumentException e) {
-                throw new IllegalArgumentException(
-                        "The request could not be authorized because the required security principal "
-                        + "was not a valid LDAP DN");
-            }
-            return authzId;
-        }
-    };
-
-    private static final Pattern DN_PATTERN = Pattern.compile("^dn:\\{[^}]+\\}$");
-
     private static final Impl DN_TEMPLATE_IMPL = new Impl() {
-
         @Override
-        public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
-                final Schema schema) {
-            return "dn:" + DN.format(t.dnFormatString, schema, templateVariables);
+        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 Pattern KEY_RE = Pattern.compile("\\{([^}]+)\\}");
-
     private static final Impl UID_TEMPLATE_IMPL = new Impl() {
-
         @Override
-        public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
-                final Schema schema) {
+        public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables) {
             return String.format(Locale.ENGLISH, t.formatString, templateVariables);
         }
-
     };
 
-    private final String dnFormatString;
+    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 new IllegalArgumentException("Invalid authorization ID template: '" + template + "'. Templates must "
+                       + "start with one of the following elements: " + joinAsString(",", getSupportedStartKeys()));
+        }
+
+        private static List<String> getSupportedStartKeys() {
+            final List<String> 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<String> keys = new ArrayList<>();
-    private final Impl pimpl;
     private final String template;
 
     /**
@@ -92,29 +117,22 @@
      * @throws IllegalArgumentException
      *             if template doesn't start with "u:" or "dn:"
      */
-    public AuthzIdTemplate(final String template) {
-        if (!template.startsWith("u:") && !template.startsWith("dn:")) {
-            throw new IllegalArgumentException("Invalid authorization ID template: " + template);
-        }
+    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 = KEY_RE.matcher(template);
+        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);
-        this.formatString = buffer.toString();
-        this.template = template;
-
-        if (template.startsWith("dn:")) {
-            this.pimpl = DN_PATTERN.matcher(template).matches() ? DN_IMPL : DN_TEMPLATE_IMPL;
-            this.dnFormatString = formatString.substring(3);
-        } else {
-            this.pimpl = UID_TEMPLATE_IMPL;
-            this.dnFormatString = null;
-        }
+        return type.removeTemplateKey(buffer.toString());
     }
 
     @Override
@@ -122,41 +140,45 @@
         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.
-     * @param schema
-     *            Schema to perform validation.
      * @return The template with all the variable replaced.
      */
-    public String formatAsAuthzId(final Map<String, Object> principals, final Schema schema) {
+    String formatAsAuthzId(final JsonValue principals) {
         final String[] templateVariables = getPrincipalsForFormatting(principals);
-        return pimpl.formatAsAuthzId(this, templateVariables, schema);
+        return type.getImpl().formatAsAuthzId(this, templateVariables);
     }
 
-    private String[] getPrincipalsForFormatting(final Map<String, Object> principals) {
+    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 Object value = principals.get(key);
-            if (isJSONPrimitive(value)) {
-                values[i] = String.valueOf(value);
-            } else if (value != null) {
-                throw new IllegalArgumentException(String.format(
-                        "The request could not be authorized because the required "
-                                + "security principal '%s' had an invalid data type", key));
-            } else {
+            final JsonValue value = principals.get(new JsonPointer(key));
+            if (value == null) {
                 throw new IllegalArgumentException(String.format(
                         "The request could not be authorized because the required "
                                 + "security principal '%s' could not be determined", key));
             }
+
+            final Object object = value.getObject();
+            if (!isJSONPrimitive(object)) {
+                throw new IllegalArgumentException(String.format(
+                        "The request could not be authorized because the required "
+                                + "security principal '%s' had an invalid data type", key));
+            }
+            values[i] = String.valueOf(object);
         }
         return values;
     }
 
-    static boolean isJSONPrimitive(final Object value) {
+    private boolean isJSONPrimitive(final Object value) {
         return value instanceof String || value instanceof Boolean || value instanceof Number;
     }
 }

--
Gitblit v1.10.0