Author: stillalex
Date: Wed Nov 22 16:00:40 2017
New Revision: 1816064

URL: http://svn.apache.org/viewvc?rev=1816064&view=rev
Log:
OAK-6818 TokenAuthentication/TokenProviderImpl: cleanup expired tokens


Added:
    
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/LoginWithTokensTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenCleanupTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/AbstractLoginTest.java
    
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
    
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/token/default.md

Modified: 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/AbstractLoginTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/AbstractLoginTest.java?rev=1816064&r1=1816063&r2=1816064&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/AbstractLoginTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/AbstractLoginTest.java
 Wed Nov 22 16:00:40 2017
@@ -108,16 +108,32 @@ abstract class AbstractLoginTest extends
         }
     }
 
+    protected boolean customConfigurationParameters() {
+        return noIterations != -1 || expiration > 0;
+    }
+
+    protected ConfigurationParameters prepare(ConfigurationParameters conf) {
+        return conf;
+    }
+
     @Override
     protected Repository[] createRepository(RepositoryFixture fixture) throws 
Exception {
-        if (noIterations != -1 || expiration > 0) {
+        if (customConfigurationParameters()) {
             if (fixture instanceof OakRepositoryFixture) {
                 return ((OakRepositoryFixture) fixture).setUpCluster(1, new 
JcrCreator() {
                     @Override
                     public Jcr customize(Oak oak) {
                         ConfigurationParameters conf;
-                        ConfigurationParameters iterations = 
ConfigurationParameters.of(UserConstants.PARAM_PASSWORD_HASH_ITERATIONS, 
noIterations);
-                        ConfigurationParameters cache = 
ConfigurationParameters.of("cacheExpiration", expiration);
+                        ConfigurationParameters iterations = 
ConfigurationParameters.EMPTY;
+                        if (noIterations != DEFAULT_ITERATIONS) {
+                            iterations = 
ConfigurationParameters.of(UserConstants.PARAM_PASSWORD_HASH_ITERATIONS,
+                                    noIterations);
+                        }
+                        ConfigurationParameters cache = 
ConfigurationParameters.EMPTY;
+                        if (expiration > 0) {
+                            cache = 
ConfigurationParameters.of("cacheExpiration", expiration);
+                        }
+
                         if (runWithToken) {
                             conf = ConfigurationParameters.of(
                                     TokenConfiguration.NAME, iterations,
@@ -126,6 +142,7 @@ abstract class AbstractLoginTest extends
                             conf = ConfigurationParameters.of(
                                     UserConfiguration.NAME, 
ConfigurationParameters.of(iterations, cache));
                         }
+                        conf = prepare(conf);
                         SecurityProvider sp = new SecurityProviderImpl(conf);
                         return new Jcr(oak).with(sp);
                     }

Modified: 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java?rev=1816064&r1=1816063&r2=1816064&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java
 Wed Nov 22 16:00:40 2017
@@ -250,6 +250,7 @@ public class BenchmarkRunner {
                     runAsUser.value(options),
                     runWithToken.value(options),
                     noIterations.value(options)),
+            new LoginWithTokensTest(numberOfUsers.value(options)),
             new LoginSystemTest(),
             new LoginImpersonateTest(),
             new LoginWithMembershipTest(

Added: 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/LoginWithTokensTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/LoginWithTokensTest.java?rev=1816064&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/LoginWithTokensTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/LoginWithTokensTest.java
 Wed Nov 22 16:00:40 2017
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.jackrabbit.oak.benchmark;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import 
org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration;
+import 
org.apache.jackrabbit.oak.spi.security.authentication.token.TokenProvider;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.jackrabbit.util.Text;
+
+/**
+ * Measure impact of synchronous token cleanup on the repository login with
+ * tokens over multiple users. Concurrency can be set via the benchmark runner.
+ *
+ * Default expiration time login tokens is 2 hours, this benchmark uses 15
+ * seconds to allow for cleanup during the benchmark.
+ *
+ */
+public class LoginWithTokensTest extends AbstractLoginTest {
+
+    private static final String REL_TEST_PATH = "testPath";
+    private static final String USER = "user";
+    private final Random r = new Random();
+
+    // defaults to 10k
+    private final int numberOfUsers;
+
+    private final long tknExpy = TimeUnit.SECONDS.toMillis(15);
+
+    private final long cleanupThreshold = 100;
+
+    public LoginWithTokensTest(int numberOfUsers) {
+        super("admin", true, DEFAULT_ITERATIONS);
+        this.numberOfUsers = numberOfUsers;
+    }
+
+    @Override
+    protected boolean customConfigurationParameters() {
+        return true;
+    }
+
+    @Override
+    protected ConfigurationParameters prepare(ConfigurationParameters conf) {
+        ConfigurationParameters tkns = 
ConfigurationParameters.of(TokenProvider.PARAM_TOKEN_EXPIRATION, tknExpy,
+                "tokenCleanupThreshold", cleanupThreshold);
+
+        ConfigurationParameters tokenConfig = 
ConfigurationParameters.of(TokenConfiguration.NAME, tkns);
+        return ConfigurationParameters.of(conf, tokenConfig);
+    }
+
+    @Override
+    public void beforeSuite() throws Exception {
+        super.beforeSuite();
+
+        Session s = loginAdministrative();
+        try {
+            UserManager userManager = ((JackrabbitSession) s).getUserManager();
+
+            for (int i = 0; i < numberOfUsers; i++) {
+                String id = USER + i;
+                userManager.createUser(id, id, new PrincipalImpl(id), 
REL_TEST_PATH);
+            }
+
+        } finally {
+            s.save();
+            s.logout();
+        }
+        System.out.println("setup done, created " + numberOfUsers + " users.");
+    }
+
+    @Override
+    public void afterSuite() throws Exception {
+        Session s = loginAdministrative();
+        try {
+            Authorizable authorizable = ((JackrabbitSession) 
s).getUserManager().getAuthorizable(USER + "0");
+            if (authorizable != null) {
+                Node n = 
s.getNode(Text.getRelativeParent(authorizable.getPath(), 1));
+                n.remove();
+            }
+
+            s.save();
+        } finally {
+            s.logout();
+        }
+    }
+
+    @Override
+    public void runTest() throws RepositoryException {
+        Repository repository = getRepository();
+        String t = USER + r.nextInt(numberOfUsers);
+        SimpleCredentials creds = new SimpleCredentials(t, t.toCharArray());
+        creds.setAttribute(".token", "");
+        repository.login(creds).logout();
+    }
+
+}

Propchange: 
jackrabbit/oak/trunk/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/LoginWithTokensTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java?rev=1816064&r1=1816063&r2=1816064&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenConfigurationImpl.java
 Wed Nov 22 16:00:40 2017
@@ -82,6 +82,13 @@ public class TokenConfigurationImpl exte
         boolean tokenRefresh() default true;
 
         @AttributeDefinition(
+                name = "Token Cleanup Threshold",
+                description = "Setting this option to a value > 0 will trigger 
a cleanup upon token creation: " +
+                        "if the number of existing token matches/exceeds the " 
+
+                        "configured value an attempt is made to removed 
expired tokens.")
+        long tokenCleanupThreshold() default 
TokenProviderImpl.NO_TOKEN_CLEANUP;
+
+        @AttributeDefinition(
                 name = "Hash Algorithm",
                 description = "Name of the algorithm to hash the token.")
         String passwordHashAlgorithm() default PasswordUtil.DEFAULT_ALGORITHM;

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java?rev=1816064&r1=1816063&r2=1816064&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
 Wed Nov 22 16:00:40 2017
@@ -98,6 +98,18 @@ class TokenProviderImpl implements Token
     private static final Logger log = 
LoggerFactory.getLogger(TokenProviderImpl.class);
 
     /**
+     * Optional configuration parameter to define the number of token nodes 
that
+     * when exceeded will trigger a cleanup of expired tokens upon creation.
+     */
+    static final String PARAM_TOKEN_CLEANUP_THRESHOLD = 
"tokenCleanupThreshold";
+
+    /**
+     * Default value indicating that tokens should never be cleaned up (i.e.
+     * backwards compatible behavior).
+     */
+    static final long NO_TOKEN_CLEANUP = 0;
+
+    /**
      * Default expiration time in ms for login tokens is 2 hours.
      */
     static final long DEFAULT_TOKEN_EXPIRATION = 2 * 3600 * 1000;
@@ -112,6 +124,7 @@ class TokenProviderImpl implements Token
     private final long tokenExpiration;
     private final UserManager userManager;
     private final IdentifierManager identifierManager;
+    private final long cleanupThreshold;
 
     TokenProviderImpl(@Nonnull Root root, @Nonnull ConfigurationParameters 
options, @Nonnull UserConfiguration userConfiguration) {
         this(root, options, userConfiguration, 
SimpleCredentialsSupport.getInstance());
@@ -125,6 +138,7 @@ class TokenProviderImpl implements Token
         this.tokenExpiration = options.getConfigValue(PARAM_TOKEN_EXPIRATION, 
DEFAULT_TOKEN_EXPIRATION);
         this.userManager = userConfiguration.getUserManager(root, 
NamePathMapper.DEFAULT);
         this.identifierManager = new IdentifierManager(root);
+        this.cleanupThreshold = 
options.getConfigValue(PARAM_TOKEN_CLEANUP_THRESHOLD, NO_TOKEN_CLEANUP);
     }
 
     //------------------------------------------------------< TokenProvider 
>---
@@ -224,6 +238,7 @@ class TokenProviderImpl implements Token
                     tokenInfo = createTokenNode(tokenParent, 
UUID.randomUUID().toString(), expTime, uuid, id, attributes);
                     root.commit(CommitMarker.asCommitAttributes());
                 }
+                cleanupExpired(userId, tokenParent, creationTime, 
tokenInfo.getToken());
                 return tokenInfo;
             } catch (NoSuchAlgorithmException | UnsupportedEncodingException 
e) {
                 // error while generating login token
@@ -276,6 +291,10 @@ class TokenProviderImpl implements Token
         return TreeUtil.getLong(tokenTree, TOKEN_ATTRIBUTE_EXPIRY, 
defaultValue);
     }
 
+    private static boolean isExpired(long expirationTime, long loginTime) {
+        return expirationTime < loginTime;
+    }
+
     private static void setExpirationTime(@Nonnull Tree tree, long time) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTimeInMillis(time);
@@ -424,7 +443,64 @@ class TokenProviderImpl implements Token
         return new TokenInfoImpl(tokenNode, token, id, null);
     }
 
-    
//--------------------------------------------------------------------------
+    /**
+     * Remove expired token nodes if the configured threshold (i.e. number of
+     * token nodes) is matched/exceeded. By default (i.e. unless configured 
with
+     * a value bigger than {@link #NO_TOKEN_CLEANUP}) no cleanup is performed
+     * and this method returns without looking at the token nodes; this makes
+     * this addition optional and will not affect existing configurations.
+     *
+     * @param parent
+     *            The token parent node.
+     * @param currentTime
+     *            The time to be used for analysing expiration of existing
+     *            tokens.
+     * @param token
+     *            The token info used as random data to skip cleanup.
+     */
+    private void cleanupExpired(@Nonnull String userId, @Nonnull Tree parent, 
long currentTime, @Nonnull String token) {
+        if (cleanupThreshold > NO_TOKEN_CLEANUP && shouldRunCleanup(token)) {
+            long start = System.currentTimeMillis();
+            long active = 0;
+            long expired = 0;
+            try {
+                if (parent.getChildrenCount(cleanupThreshold) >= 
cleanupThreshold) {
+                    for (Tree child : parent.getChildren()) {
+                        if (isExpired(getExpirationTime(child, 
Long.MIN_VALUE), currentTime)) {
+                            expired++;
+                            child.remove();
+                        } else {
+                            active++;
+                        }
+                    }
+                }
+                if (root.hasPendingChanges()) {
+                    root.commit(CommitMarker.asCommitAttributes());
+                }
+            } catch (CommitFailedException e) {
+                log.debug("Failed to cleanup expired token nodes", e);
+                root.refresh();
+            } finally {
+                if (log.isDebugEnabled() && active + expired > 0) {
+                    log.debug("Token cleanup completed in {} ms: removed {}/{} 
tokens for {}.",
+                            System.currentTimeMillis() - start, expired, 
active + expired, userId);
+                }
+            }
+        }
+    }
+
+    /**
+     * Method that determines if the cleanup should run or not based on the
+     * randomly generated token's first char, this decreases the chances to 
1/8.
+     *
+     * @param tkn
+     * @return true if the cleanup should run
+     */
+    static boolean shouldRunCleanup(@Nonnull String token) {
+        return token.charAt(0) < '2';
+    }
+
+    // 
--------------------------------------------------------------------------
 
     /**
      * TokenInfo
@@ -489,7 +565,7 @@ class TokenProviderImpl implements Token
 
         @Override
         public boolean isExpired(long loginTime) {
-            return expirationTime < loginTime;
+            return TokenProviderImpl.isExpired(expirationTime, loginTime);
         }
 
         @Override

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenCleanupTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenCleanupTest.java?rev=1816064&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenCleanupTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenCleanupTest.java
 Wed Nov 22 16:00:40 2017
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.jackrabbit.oak.security.authentication.token;
+
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import 
org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConstants;
+import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenInfo;
+import 
org.apache.jackrabbit.oak.spi.security.authentication.token.TokenProvider;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TokenCleanupTest extends AbstractTokenTest {
+
+    private String userId;
+
+    @Override
+    public void before() throws Exception {
+        super.before();
+        userId = getTestUser().getID();
+    }
+
+    @Override
+    ConfigurationParameters getTokenConfig() {
+        return 
ConfigurationParameters.of(TokenProviderImpl.PARAM_TOKEN_CLEANUP_THRESHOLD, 5);
+    }
+
+    private void assertTokenNodes(int expectedNumber) throws Exception {
+        Tree tokenParent = root.getTree(getTestUser().getPath() + '/' + 
TokenConstants.TOKENS_NODE_NAME);
+        assertEquals(expectedNumber, 
tokenParent.getChildrenCount(expectedNumber*2));
+    }
+
+    private void createExpiredTokens(int numberOfTokens) throws Exception {
+        for (int i = 0; i < numberOfTokens; i++) {
+            TokenInfo tokenInfo = tokenProvider.createToken(userId, 
ImmutableMap.of(TokenProvider.PARAM_TOKEN_EXPIRATION, 2));
+            // wait until the info created has expired
+            if (tokenInfo != null) {
+                waitUntilExpired(tokenInfo);
+            }
+        }
+    }
+
+    private int createTokensUntilCleanup() throws Exception {
+        int tkn = 0;
+        boolean clean = false;
+        while (!clean && tkn < 50) {
+            TokenInfo tokenInfo = tokenProvider.createToken(userId, 
ImmutableMap.of());
+            clean = TokenProviderImpl.shouldRunCleanup(tokenInfo.getToken());
+            tkn++;
+        }
+        return tkn;
+    }
+
+    private void waitUntilExpired(@Nonnull TokenInfo info) {
+        long now = System.currentTimeMillis();
+        while (!info.isExpired(now)) {
+            now = waitForSystemTimeIncrement(now);
+        }
+    }
+
+    @Test
+    public void testExpiredBelowThreshold() throws Exception {
+        createExpiredTokens(4);
+        assertTokenNodes(4);
+    }
+
+    @Test
+    public void testAllExpiredReachingThreshold() throws Exception {
+        createExpiredTokens(5);
+        int extras = createTokensUntilCleanup();
+        assertTokenNodes(extras);
+    }
+
+    @Test
+    public void testSomeExpiredReachingThreshold() throws Exception {
+        createExpiredTokens(3);
+        tokenProvider.createToken(userId, ImmutableMap.of());
+
+        assertTokenNodes(4);
+
+        int extras = createTokensUntilCleanup();
+        assertTokenNodes(1 + extras);
+    }
+
+    @Test
+    public void testNotExpiredReachingThreshold() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            tokenProvider.createToken(userId, ImmutableMap.of());
+        }
+        assertTokenNodes(10);
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenCleanupTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/token/default.md
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/token/default.md?rev=1816064&r1=1816063&r2=1816064&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/token/default.md
 (original)
+++ 
jackrabbit/oak/trunk/oak-doc/src/site/markdown/security/authentication/token/default.md
 Wed Nov 22 16:00:40 2017
@@ -97,6 +97,15 @@ that the expiration time has not been re
 and the user will need to login again using the configured login
 mechanism (e.g. using the credentials support for token creation).
 
+#### Token Cleanup
+
+Automatic token cleanup can be enabled by setting the `tokenCleanupThreshold` 
parameter
+to a value larger than `0` (`0` means disabled). This will trigger a cleanup 
call if
+the number of tokens under a user exceeds this value. (As an implementation 
detail a
+throttling method was introduced to only allow the call to go through 1/8 
times).
+
+This is available with Oak 1.7.12 on, see also [OAK-6818]for additional 
information.
+
 <a name="representation"/>
 ### Representation in the Repository
 
@@ -195,7 +204,7 @@ all of type `Constraint` with the follow
 <a name="configuration"/>
 ### Configuration
 
-The default Oak [TokenConfiguration] allows to define the following 
configuration
+The default Oak `TokenConfiguration` allows to define the following 
configuration
 options for the `TokenProvider`:
 
 #### Configuration Parameters
@@ -208,6 +217,7 @@ options for the `TokenProvider`:
 | PARAM_PASSWORD_HASH_ALGORITHM       | String  | SHA-256                  |
 | PARAM_PASSWORD_HASH_ITERATIONS      | int     | 1000                     |
 | PARAM_PASSWORD_SALT_SIZE            | int     | 8                        |
+| PARAM_TOKEN_CLEANUP_THRESHOLD       | long    | 0 (no cleanup)           |
 | | | |
 
 


Reply via email to