[java-identity-provider] branch main updated: IDP-2467 LdapPrincipal account state support

Codeberg noreply at shibboleth.net
Wed Jul 1 03:01:59 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
https://codeberg.org/Shibboleth/java-identity-provider/commit/c60924e9a1a00945b8a87ea63b423c591ae01400

The following commit(s) were added to refs/heads/main by this push:
     new c60924e9a IDP-2467 LdapPrincipal account state support
c60924e9a is described below

commit c60924e9a1a00945b8a87ea63b423c591ae01400
Author: Daniel Fisher <dfisher at vt.edu>
AuthorDate: Tue Jun 30 22:22:34 2026 -0400

    IDP-2467 LdapPrincipal account state support
    
    https://shibboleth.atlassian.net/browse/IDP-2467
    
    Update LDAPCredentialValidator to set account state.
    Update LDAPPrincipalSerializer to support account state.
---
 .../idp/authn/impl/LDAPCredentialValidator.java    |   5 +-
 .../principal/impl/LDAPPrincipalSerializer.java    |  98 +++++++++++-
 .../DefaultAuthenticationResultSerializerTest.java |  55 +++++++
 .../impl/LDAPPrincipalSerializerTest.java          | 172 +++++++++++++++++++++
 .../impl/LDAPAuthenticationAccountStateResult.json |   1 +
 5 files changed, 324 insertions(+), 7 deletions(-)

diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
index 426202fed..28c3b021b 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
@@ -223,7 +223,10 @@ public class LDAPCredentialValidator extends AbstractUsernamePasswordCredentialV
         
         final Subject subject = new Subject();
         subject.getPrincipals().add(
-                new LdapPrincipal(usernamePasswordContext.getTransformedUsername(), ldapResponse.getLdapEntry()));
+            new LdapPrincipal(
+                usernamePasswordContext.getTransformedUsername(),
+                ldapResponse.getLdapEntry(),
+                ldapResponse.getAccountState()));
         return super.populateSubject(subject, usernamePasswordContext);
     }
 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
index d72e9171d..cdb6b315c 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
@@ -18,14 +18,18 @@ import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.security.Principal;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
+import javax.security.auth.login.LoginException;
 import jakarta.json.Json;
-import jakarta.json.JsonArray;
 import jakarta.json.JsonArrayBuilder;
 import jakarta.json.JsonBuilderFactory;
 import jakarta.json.JsonException;
@@ -39,6 +43,7 @@ import jakarta.json.stream.JsonGenerator;
 
 import org.ldaptive.LdapAttribute;
 import org.ldaptive.LdapEntry;
+import org.ldaptive.auth.AccountState;
 import org.ldaptive.jaas.LdapPrincipal;
 import org.slf4j.Logger;
 
@@ -60,13 +65,17 @@ public class LDAPPrincipalSerializer extends AbstractPrincipalSerializer<String>
     /** Field name of principal entry. */
     @Nonnull @NotEmpty private static final String PRINCIPAL_ENTRY_FIELD = "LDAPE";
 
+    /** Field name of principal account state. */
+    @Nonnull @NotEmpty private static final String PRINCIPAL_ACCOUNT_STATE_FIELD = "LDAPAS";
+
     /** Pattern used to determine if input is supported. */
-    private static final Pattern JSON_PATTERN = Pattern.compile("^\\{\"LDAPN\":.*,\"LDAPE\":.*\\}$");
+    private static final Pattern JSON_PATTERN = Pattern.compile(
+      "^\\{(\"LDAPN\":.+)(,\"LDAPE\":.+)?(,\"LDAPAS\":.+)?\\}$");
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(LDAPPrincipalSerializer.class);
     
-    /** JSON object bulder factory. */
+    /** JSON object builder factory. */
     @Nonnull private final JsonBuilderFactory objectBuilderFactory;
 
     /** Constructor. */
@@ -100,6 +109,34 @@ public class LDAPPrincipalSerializer extends AbstractPrincipalSerializer<String>
                 }
                 gen.write(PRINCIPAL_ENTRY_FIELD, objectBuilder.build());
             }
+            final AccountState accountState = ((LdapPrincipal) principal).getAccountState();
+            if (accountState != null) {
+                final JsonObjectBuilder objectBuilder = getJsonObjectBuilder();
+                if (accountState.getWarnings() != null) {
+                    final JsonArrayBuilder warningsBuilder = getJsonArrayBuilder();
+                    for (final AccountState.Warning warning : accountState.getWarnings()) {
+                        final JsonObjectBuilder warningBuilder = getJsonObjectBuilder();
+                        if (warning.getExpiration() != null) {
+                            warningBuilder.add(
+                                "exp", warning.getExpiration().format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+                        }
+                        warningBuilder.add("loginsRemaining", warning.getLoginsRemaining());
+                        warningsBuilder.add(warningBuilder);
+                    }
+                    objectBuilder.add("warnings", warningsBuilder.build());
+                }
+                if (accountState.getErrors() != null) {
+                    final JsonArrayBuilder errorsBuilder = getJsonArrayBuilder();
+                    for (final AccountState.Error error : accountState.getErrors()) {
+                        final JsonObjectBuilder errorBuilder = getJsonObjectBuilder();
+                        errorBuilder.add("code", error.getCode());
+                        errorBuilder.add("msg", error.getMessage());
+                        errorsBuilder.add(errorBuilder);
+                    }
+                    objectBuilder.add("errors", errorsBuilder.build());
+                }
+                gen.write(PRINCIPAL_ACCOUNT_STATE_FIELD, objectBuilder.build());
+            }
             gen.writeEnd();
         }
         final String result = sink.toString();
@@ -122,7 +159,7 @@ public class LDAPPrincipalSerializer extends AbstractPrincipalSerializer<String>
                 throw new IOException("Found invalid data structure while parsing LdapPrincipal");
             }
             
-            final JsonObject obj = (JsonObject) st;
+            final JsonObject obj = st.asJsonObject();
             final JsonString str = obj.getJsonString(PRINCIPAL_NAME_FIELD);
             if (str != null) {
                 if (!Strings.isNullOrEmpty(str.getString())) {
@@ -135,14 +172,45 @@ public class LDAPPrincipalSerializer extends AbstractPrincipalSerializer<String>
                                 entry.setDn(((JsonString) e.getValue()).getString());
                             } else {
                                 final LdapAttribute attr = new LdapAttribute(e.getKey());
-                                for (final JsonValue v : (JsonArray) e.getValue()) {
+                                for (final JsonValue v : e.getValue().asJsonArray()) {
                                     attr.addStringValues(((JsonString) v).getString());
                                 }
                                 entry.addAttributes(attr);
                             }
                         }
                     }
-                    return new LdapPrincipal(str.getString(), entry);
+                    AccountState accountState = null;
+                    final JsonObject jsonAccountState = obj.getJsonObject(PRINCIPAL_ACCOUNT_STATE_FIELD);
+                    if (jsonAccountState != null) {
+                        final List<AccountState.Error> errors = new ArrayList<>();
+                        final List<AccountState.Warning> warnings = new ArrayList<>();
+                        for (final Map.Entry<String, JsonValue> e : jsonAccountState.entrySet()) {
+                            if ("errors".equalsIgnoreCase(e.getKey())) {
+                                for (final JsonValue v : e.getValue().asJsonArray()) {
+                                    final JsonObject o = v.asJsonObject();
+                                    errors.add(new DeserializedError(o.getInt("code"), o.getString("msg")));
+                                }
+                            } else if ("warnings".equalsIgnoreCase(e.getKey())) {
+                                for (final JsonValue v : e.getValue().asJsonArray()) {
+                                    final JsonObject o = v.asJsonObject();
+                                    ZonedDateTime zdt = null;
+                                    if (o.containsKey("exp")) {
+                                        zdt = ZonedDateTime.parse(
+                                            o.getString("exp"), DateTimeFormatter.ISO_ZONED_DATE_TIME);
+                                    }
+                                    int lr = -1;
+                                    if (o.containsKey("loginsRemaining")) {
+                                        lr = o.getInt("loginsRemaining");
+                                    }
+                                    warnings.add(new AccountState.DefaultWarning(zdt, lr));
+                                }
+                            }
+                        }
+                        accountState = new AccountState(
+                            warnings.isEmpty() ? null : warnings.toArray(new AccountState.Warning[0]),
+                            errors.isEmpty() ? null : errors.toArray(new AccountState.Error[0]));
+                    }
+                    return new LdapPrincipal(str.getString(), entry, accountState);
                 }
                 log.warn("Skipping null/empty LdapPrincipal");
             }
@@ -174,4 +242,22 @@ public class LDAPPrincipalSerializer extends AbstractPrincipalSerializer<String>
         return result;
     }
 
+    /** Error implementation that stores deserialized properties. */
+    private record DeserializedError(int code, String message) implements AccountState.Error {
+
+        @Override
+        public int getCode() {
+            return code;
+        }
+
+        @Override
+        public String getMessage() {
+            return message;
+        }
+
+        @Override
+        public void throwSecurityException() throws LoginException {
+            throw new UnsupportedOperationException("This method is not supported");
+        }
+    }
 }
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
index e53036c6e..5dd83019f 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
@@ -31,6 +31,7 @@ import javax.security.auth.Subject;
 
 import org.ldaptive.LdapAttribute;
 import org.ldaptive.LdapEntry;
+import org.ldaptive.auth.ext.PasswordPolicyAccountState;
 import org.ldaptive.jaas.LdapPrincipal;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.testing.RequestContextBuilder;
@@ -413,6 +414,60 @@ public class DefaultAuthenticationResultSerializerTest {
         assertTrue(result2.getReuseCondition().test(prc));
     }
 
+    @Test public void testLdapWithAccountState() throws Exception {
+        serializer.initialize();
+        flowDescriptor.initialize();
+
+        final AuthenticationResult result = createResult(flowDescriptor, new Subject());
+        final LdapEntry entry = new LdapEntry();
+        entry.setDn("uid=1234,ou=people,dc=shibboleth,dc=net");
+        final LdapAttribute givenName = new LdapAttribute();
+        givenName.setName("givenName");
+        givenName.addStringValues("Bob", "Robert");
+        entry.addAttributes(
+                new LdapAttribute("cn", "Bob Cobb"),
+                givenName,
+                new LdapAttribute("sn", "Cobb"),
+                new LdapAttribute("mail", "bob at shibboleth.net"));
+        final PasswordPolicyAccountState accountState = new PasswordPolicyAccountState(5);
+        result.getSubject().getPrincipals().add(new LdapPrincipal("bob", LdapEntry.sort(entry), accountState));
+
+        final ProfileRequestContext prc = getProfileRequestContext(CollectionSupport.singletonList(flowDescriptor));
+        assertTrue(result.getReuseCondition().test(prc));
+
+        final String s = flowDescriptor.serialize(result);
+        final String s2 = fileToString(DATAPATH + "LDAPAuthenticationAccountStateResult.json");
+        assertEquals(s, s2);
+
+        final AuthenticationResult result2 = flowDescriptor.deserialize(1, CONTEXT, KEY, s2,
+                Instant.ofEpochMilli(ACTIVITY)
+                    .plus(flowDescriptor.getInactivityTimeout())
+                    .plus(AuthenticationFlowDescriptor.STORAGE_EXPIRATION_OFFSET)
+                    .toEpochMilli());
+
+        assertEquals(result.getAuthenticationFlowId(), result2.getAuthenticationFlowId());
+        assertEquals(result.getAuthenticationInstant(), result2.getAuthenticationInstant());
+        assertEquals(result.getLastActivityInstant(), result2.getLastActivityInstant());
+        assertEquals(result.getSubject(), result2.getSubject());
+        assertEquals(
+                ((LdapPrincipal) result.getSubject().getPrincipals().iterator().next()).getLdapEntry(),
+                ((LdapPrincipal) result2.getSubject().getPrincipals().iterator().next()).getLdapEntry());
+        assertEquals(
+                ((LdapPrincipal) result.getSubject().getPrincipals().iterator().next()).getAccountState().getError(),
+                ((LdapPrincipal) result2.getSubject().getPrincipals().iterator().next()).getAccountState().getError());
+        assertEquals(
+                ((LdapPrincipal) result.getSubject().getPrincipals().iterator().next())
+                    .getAccountState().getWarning().getExpiration(),
+                ((LdapPrincipal) result2.getSubject().getPrincipals().iterator().next())
+                    .getAccountState().getWarning().getExpiration());
+        assertEquals(
+                ((LdapPrincipal) result.getSubject().getPrincipals().iterator().next())
+                    .getAccountState().getWarning().getLoginsRemaining(),
+                ((LdapPrincipal) result2.getSubject().getPrincipals().iterator().next())
+                    .getAccountState().getWarning().getLoginsRemaining());
+        assertTrue(result2.getReuseCondition().test(prc));
+    }
+
     @Test public void testIdPAttribute() throws Exception {
         serializer.initialize();
         flowDescriptor.initialize();
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializerTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializerTest.java
new file mode 100644
index 000000000..904c2108c
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.authn.principal.impl;
+
+import java.io.IOException;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.ldaptive.LdapAttribute;
+import org.ldaptive.LdapEntry;
+import org.ldaptive.auth.ext.PasswordPolicyAccountState;
+import org.ldaptive.control.PasswordPolicyControl;
+import org.ldaptive.jaas.LdapPrincipal;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/** Unit test for {@link LDAPPrincipalSerializer}. */
+ at SuppressWarnings("javadoc")
+public class LDAPPrincipalSerializerTest {
+
+    LDAPPrincipalSerializer serializer;
+
+    @BeforeClass
+    public void setUp() {
+        serializer = new LDAPPrincipalSerializer();
+    }
+
+    @Test
+    public void testRoundTrip() throws IOException {
+        final LdapPrincipal p1 = new LdapPrincipal("alice", null);
+        final String s = serializer.serialize(p1);
+        Assert.assertEquals(s, "{\"LDAPN\":\"alice\"}");
+        Assert.assertTrue(serializer.supports(s));
+        final LdapPrincipal p2 = serializer.deserialize(s);
+        Assert.assertEquals(p1, p2);
+        assert p2 != null;
+        Assert.assertEquals(p2.getName(), "alice");
+        Assert.assertNull(p2.getLdapEntry());
+        Assert.assertNull(p2.getAccountState());
+    }
+
+    @Test
+    public void testRoundTripWithEntry() throws IOException {
+        final LdapEntry entry = new LdapEntry();
+        entry.setDn("uid=alice");
+        entry.addAttributes(new LdapAttribute("uid", "alice"), new LdapAttribute("givenName", "alice"));
+        final LdapPrincipal p1 = new LdapPrincipal("alice", entry);
+        final String s = serializer.serialize(p1);
+        Assert.assertEquals(
+            s,
+            "{\"LDAPN\":\"alice\",\"LDAPE\":{\"dn\":\"uid=alice\",\"uid\":[\"alice\"],\"givenName\":[\"alice\"]}}");
+        Assert.assertTrue(serializer.supports(s));
+        final LdapPrincipal p2 = serializer.deserialize(s);
+        Assert.assertEquals(p1, p2);
+        assert p2 != null;
+        Assert.assertEquals(p2.getName(), "alice");
+        Assert.assertEquals(p2.getLdapEntry(), entry);
+        Assert.assertNull(p2.getAccountState());
+    }
+
+    @Test
+    public void testSerializeWithEntryAndAccountStateError() throws IOException {
+        final LdapEntry entry = new LdapEntry();
+        entry.setDn("uid=alice");
+        entry.addAttributes(new LdapAttribute("uid", "alice"), new LdapAttribute("givenName", "alice"));
+        final PasswordPolicyAccountState accountState = new PasswordPolicyAccountState(
+            PasswordPolicyControl.Error.PASSWORD_EXPIRED);
+        final LdapPrincipal p1 = new LdapPrincipal("alice", entry, accountState);
+        final String s = serializer.serialize(p1);
+        Assert.assertEquals(
+            s,
+            "{" +
+                "\"LDAPN\":\"alice\"," +
+                "\"LDAPE\":{\"dn\":\"uid=alice\",\"uid\":[\"alice\"],\"givenName\":[\"alice\"]}," +
+                "\"LDAPAS\":" +
+                    "{" +
+                        "\"errors\":[{\"code\":0,\"msg\":\"PASSWORD_EXPIRED\"}]" +
+                    "}" +
+            "}");
+        Assert.assertTrue(serializer.supports(s));
+        final LdapPrincipal p2 = serializer.deserialize(s);
+        Assert.assertEquals(p1, p2);
+        assert p2 != null;
+        Assert.assertEquals(p2.getName(), "alice");
+        Assert.assertEquals(p2.getLdapEntry(), entry);
+        Assert.assertNotNull(p2.getAccountState());
+        Assert.assertNull(p2.getAccountState().getWarning());
+        Assert.assertEquals(p2.getAccountState().getError().getCode(), 0);
+        Assert.assertEquals(p2.getAccountState().getError().getMessage(), "PASSWORD_EXPIRED");
+    }
+
+    @Test
+    public void testSerializeWithEntryAndAccountStateWarning() throws IOException {
+        final LdapEntry entry = new LdapEntry();
+        entry.setDn("uid=alice");
+        entry.addAttributes(new LdapAttribute("uid", "alice"), new LdapAttribute("givenName", "alice"));
+        final ZonedDateTime exp = ZonedDateTime.of(2025, 5, 25, 12, 35, 0, 0, ZoneId.of("UTC"));
+        final PasswordPolicyAccountState accountState = new PasswordPolicyAccountState(exp);
+        final LdapPrincipal p1 = new LdapPrincipal("alice", entry, accountState);
+        final String s = serializer.serialize(p1);
+        Assert.assertEquals(
+            s,
+            "{" +
+                "\"LDAPN\":\"alice\"," +
+                "\"LDAPE\":{\"dn\":\"uid=alice\",\"uid\":[\"alice\"],\"givenName\":[\"alice\"]}," +
+                "\"LDAPAS\":" +
+                    "{" +
+                        "\"warnings\":[{\"exp\":\"2025-05-25T12:35:00Z[UTC]\",\"loginsRemaining\":-1}]" +
+                    "}" +
+            "}");
+        Assert.assertTrue(serializer.supports(s));
+        final LdapPrincipal p2 = serializer.deserialize(s);
+        Assert.assertEquals(p1, p2);
+        assert p2 != null;
+        Assert.assertEquals(p2.getName(), "alice");
+        Assert.assertEquals(p2.getLdapEntry(), entry);
+        Assert.assertNotNull(p2.getAccountState());
+        Assert.assertNull(p2.getAccountState().getError());
+        Assert.assertEquals(p2.getAccountState().getWarning().getExpiration(), exp);
+        Assert.assertEquals(p2.getAccountState().getWarning().getLoginsRemaining(), -1);
+    }
+
+    @Test
+    public void testSerializeWithEntryAndAccountStateAll() throws IOException {
+        final LdapEntry entry = new LdapEntry();
+        entry.setDn("uid=alice");
+        entry.addAttributes(new LdapAttribute("uid", "alice"), new LdapAttribute("givenName", "alice"));
+        final ZonedDateTime exp = ZonedDateTime.of(2025, 5, 25, 12, 35, 0, 0, ZoneId.of("UTC"));
+        final PasswordPolicyAccountState accountState = new PasswordPolicyAccountState(
+            exp,
+            PasswordPolicyControl.Error.PASSWORD_EXPIRED);
+        final LdapPrincipal p1 = new LdapPrincipal("alice", entry, accountState);
+        final String s = serializer.serialize(p1);
+        Assert.assertEquals(
+            s,
+            "{" +
+                "\"LDAPN\":\"alice\"," +
+                "\"LDAPE\":{\"dn\":\"uid=alice\",\"uid\":[\"alice\"],\"givenName\":[\"alice\"]}," +
+                "\"LDAPAS\":" +
+                    "{" +
+                        "\"warnings\":[{\"exp\":\"2025-05-25T12:35:00Z[UTC]\",\"loginsRemaining\":-1}]," +
+                        "\"errors\":[{\"code\":0,\"msg\":\"PASSWORD_EXPIRED\"}]" +
+                    "}" +
+            "}");
+        Assert.assertTrue(serializer.supports(s));
+        final LdapPrincipal p2 = serializer.deserialize(s);
+        Assert.assertEquals(p1, p2);
+        assert p2 != null;
+        Assert.assertEquals(p2.getName(), "alice");
+        Assert.assertEquals(p2.getLdapEntry(), entry);
+        Assert.assertNotNull(p2.getAccountState());
+        Assert.assertEquals(p2.getAccountState().getWarning().getExpiration(), exp);
+        Assert.assertEquals(p2.getAccountState().getWarning().getLoginsRemaining(), -1);
+        Assert.assertEquals(p2.getAccountState().getError().getCode(), 0);
+        Assert.assertEquals(p2.getAccountState().getError().getMessage(), "PASSWORD_EXPIRED");
+    }
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/LDAPAuthenticationAccountStateResult.json b/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/LDAPAuthenticationAccountStateResult.json
new file mode 100644
index 000000000..6b4f67147
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/LDAPAuthenticationAccountStateResult.json
@@ -0,0 +1 @@
+{"id":"test","ts":1378827849463,"princ":[{"LDAPN":"bob","LDAPE":{"dn":"uid=1234,ou=people,dc=shibboleth,dc=net","cn":["Bob Cobb"],"givenName":["Bob","Robert"],"mail":["bob at shibboleth.net"],"sn":["Cobb"]},"LDAPAS":{"warnings":[{"loginsRemaining":5}]}}]}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list