com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor.java

Source

/*
 * Copyright (c) 2010-2017 Evolveum
 *
 * 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 com.evolveum.midpoint.model.common.stringpolicy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;

import com.evolveum.midpoint.schema.util.LocalizationUtil;
import com.evolveum.midpoint.util.LocalizableMessage;
import com.evolveum.midpoint.util.LocalizableMessageBuilder;
import com.evolveum.midpoint.util.LocalizableMessageList;
import com.evolveum.midpoint.util.LocalizableMessageListBuilder;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.mutable.MutableBoolean;
import org.apache.commons.lang.text.StrBuilder;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.repo.common.expression.ExpressionFactory;
import com.evolveum.midpoint.repo.common.expression.ExpressionUtil;
import com.evolveum.midpoint.repo.common.expression.ExpressionVariables;
import com.evolveum.midpoint.schema.ResultHandler;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CharacterClassType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CheckExpressionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LimitationsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordLifeTimeType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ProhibitedValueItemType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ProhibitedValuesType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.StringLimitType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.StringPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;

import static org.apache.commons.lang3.BooleanUtils.isTrue;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;

/**
 * Processor for values that match value policies (mostly passwords).
 * This class is supposed to process the parts of the value policy
 * as defined in the ValuePolicyType. So it will validate the values
 * and generate the values. It is NOT supposed to process
 * more complex credential policies such as password lifetime
 * and history.
 *
 *  @author mamut
 *  @author semancik
 */

@Component
public class ValuePolicyProcessor {

    private static final String OP_GENERATE = ValuePolicyProcessor.class.getName() + ".generate";
    private static final transient Trace LOGGER = TraceManager.getTrace(ValuePolicyProcessor.class);

    private static final Random RAND = new Random(System.currentTimeMillis());

    private static final String DOT_CLASS = ValuePolicyProcessor.class.getName() + ".";
    private static final String OPERATION_STRING_POLICY_VALIDATION = DOT_CLASS + "stringPolicyValidation";
    private static final int DEFAULT_MAX_ATTEMPTS = 10;

    @Autowired
    private ExpressionFactory expressionFactory;
    @Autowired
    private Protector protector;

    static class Context {
        final ItemPath path;

        public Context(ItemPath path) {
            this.path = defaultIfNull(path, SchemaConstants.PATH_PASSWORD_VALUE);
        }
    }

    public ExpressionFactory getExpressionFactory() {
        return expressionFactory;
    }

    // Used in tests
    public void setExpressionFactory(ExpressionFactory expressionFactory) {
        this.expressionFactory = expressionFactory;
    }

    public <O extends ObjectType> String generate(ItemPath path, ValuePolicyType policy, int defaultLength,
            boolean generateMinimalSize, AbstractValuePolicyOriginResolver<O> originResolver, String shortDesc,
            Task task, OperationResult parentResult) throws ExpressionEvaluationException, SchemaException,
            ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
        Context ctx = new Context(path);
        OperationResult result = parentResult.createSubresult(OP_GENERATE);

        if (policy == null) {
            //lets create some default policy
            policy = new ValuePolicyType().stringPolicy(new StringPolicyType()
                    .limitations(new LimitationsType().maxLength(defaultLength).minLength(defaultLength)));

        }

        StringPolicyType stringPolicy = policy.getStringPolicy();
        int maxAttempts = DEFAULT_MAX_ATTEMPTS;
        if (stringPolicy.getLimitations() != null && stringPolicy.getLimitations().getMaxAttempts() != null) {
            maxAttempts = stringPolicy.getLimitations().getMaxAttempts();
        }
        if (maxAttempts < 1) {
            ExpressionEvaluationException e = new ExpressionEvaluationException(
                    "Illegal number of maximum value generation attempts: " + maxAttempts);
            result.recordFatalError(e);
            throw e;
        }
        String generatedValue;
        int attempt = 1;
        for (;;) {
            generatedValue = generateAttempt(policy, defaultLength, generateMinimalSize, ctx, result);
            if (result.isError()) {
                throw new ExpressionEvaluationException(result.getMessage());
            }
            if (checkAttempt(generatedValue, policy, originResolver, shortDesc, task, result)) {
                break;
            }
            LOGGER.trace("Generator attempt {}: check failed", attempt);
            if (attempt == maxAttempts) {
                ExpressionEvaluationException e = new ExpressionEvaluationException(
                        "Unable to generate value, maximum number of attempts exceeded");
                result.recordFatalError(e);
                throw e;
            }
            attempt++;
        }
        return generatedValue;
    }

    public <O extends ObjectType> boolean validateValue(String newValue, ValuePolicyType pp,
            AbstractValuePolicyOriginResolver<O> originResolver, String shortDesc, Task task,
            OperationResult parentResult)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        return validateValue(newValue, pp, originResolver, new ArrayList<>(), shortDesc, task, parentResult);
    }

    public <O extends ObjectType> boolean validateValue(String newValue, ValuePolicyType pp,
            AbstractValuePolicyOriginResolver<O> originResolver, List<LocalizableMessage> messages,
            String shortDesc, Task task, OperationResult parentResult)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        //TODO: do we want to throw exception when no value policy defined??
        Validate.notNull(pp, "Value policy must not be null.");

        OperationResult result = parentResult.createSubresult(OPERATION_STRING_POLICY_VALIDATION);
        result.addArbitraryObjectAsParam("policyName", pp.getName());
        normalize(pp);

        if (newValue == null && defaultIfNull(XsdTypeMapper.multiplicityToInteger(pp.getMinOccurs()), 0) == 0) {
            // No value is allowed
            result.recordSuccess();
            return true;
        }

        if (newValue == null) {
            newValue = "";
        }

        LimitationsType lims = pp.getStringPolicy().getLimitations();

        testMinimalLength(newValue, lims, result, messages);
        testMaximalLength(newValue, lims, result, messages);

        testMinimalUniqueCharacters(newValue, lims, result, messages);

        testProhibitedValues(newValue, pp.getProhibitedValues(), originResolver, shortDesc, task, result, messages);
        testCheckExpression(newValue, lims, originResolver, shortDesc, task, result, messages);

        if (!lims.getLimit().isEmpty()) {
            // check limitation
            HashSet<String> validChars;
            HashSet<String> allValidChars = new HashSet<>();
            List<String> characters = StringPolicyUtils.stringTokenizer(newValue);
            for (StringLimitType stringLimitationType : lims.getLimit()) {
                OperationResult limitResult = new OperationResult(
                        "Tested limitation: " + stringLimitationType.getDescription());

                validChars = getValidCharacters(stringLimitationType.getCharacterClass(), pp);
                int count = countValidCharacters(validChars, characters);
                allValidChars.addAll(validChars);
                testMinimalOccurrence(stringLimitationType, count, limitResult, messages);
                testMaximalOccurrence(stringLimitationType, count, limitResult, messages);
                testMustBeFirst(stringLimitationType, limitResult, messages, newValue, validChars);

                limitResult.computeStatus();
                result.addSubresult(limitResult);
            }
            testInvalidCharacters(characters, allValidChars, result, messages);
        }

        result.computeStatus();
        if (!result.isSuccess() && !messages.isEmpty()) {
            result.setUserFriendlyMessage(new LocalizableMessageListBuilder().messages(messages)
                    .separator(LocalizableMessageList.SPACE).buildOptimized());
        }
        return result.isAcceptable();
    }

    /**
     * add defined default values
     */
    private void normalize(ValuePolicyType pp) {
        if (null == pp) {
            throw new IllegalArgumentException("Password policy cannot be null");
        }

        if (null == pp.getStringPolicy()) {
            StringPolicyType sp = new StringPolicyType();
            pp.setStringPolicy(StringPolicyUtils.normalize(sp));
        } else {
            pp.setStringPolicy(StringPolicyUtils.normalize(pp.getStringPolicy()));
        }

        if (null == pp.getLifetime()) {
            PasswordLifeTimeType lt = new PasswordLifeTimeType();
            lt.setExpiration(-1);
            lt.setWarnBeforeExpiration(0);
            lt.setLockAfterExpiration(0);
            lt.setMinPasswordAge(0);
            lt.setPasswordHistoryLength(0);
        }
    }

    private void testMustBeFirst(StringLimitType stringLimitation, OperationResult result,
            List<LocalizableMessage> messages, String value, Set<String> validFirstChars) {
        if (StringUtils.isNotEmpty(value) && isTrue(stringLimitation.isMustBeFirst())
                && !validFirstChars.contains(value.substring(0, 1))) {
            LocalizableMessage msg = new LocalizableMessageBuilder().key("ValuePolicy.firstCharacterNotAllowed")
                    .arg(validFirstChars.toString()).build();
            result.addSubresult(
                    new OperationResult("Check valid first char", OperationResultStatus.FATAL_ERROR, msg));
            messages.add(msg);
        }
    }

    private void testMaximalOccurrence(StringLimitType stringLimitation, int count, OperationResult result,
            List<LocalizableMessage> messages) {
        if (stringLimitation.getMaxOccurs() == null) {
            return;
        }
        if (count > stringLimitation.getMaxOccurs()) {
            LocalizableMessage msg = new LocalizableMessageBuilder().key("ValuePolicy.maximalOccurrenceExceeded")
                    .arg(stringLimitation.getMaxOccurs()).arg(stringLimitation.getDescription()).arg(count).build();
            result.addSubresult(new OperationResult("Check maximal occurrence of characters",
                    OperationResultStatus.FATAL_ERROR, msg));
            messages.add(msg);
        }
    }

    private void testMinimalOccurrence(StringLimitType stringLimitation, int count, OperationResult result,
            List<LocalizableMessage> messages) {
        if (stringLimitation.getMinOccurs() == null) {
            return;
        }
        if (count < stringLimitation.getMinOccurs()) {
            LocalizableMessage msg = new LocalizableMessageBuilder().key("ValuePolicy.minimalOccurrenceNotMet")
                    .arg(stringLimitation.getMinOccurs()).arg(stringLimitation.getDescription()).arg(count).build();
            result.addSubresult(new OperationResult("Check minimal occurrence of characters",
                    OperationResultStatus.FATAL_ERROR, msg));
            messages.add(msg);
        }
    }

    private int countValidCharacters(Set<String> validChars, List<String> password) {
        int count = 0;
        for (String s : password) {
            if (validChars.contains(s)) {
                count++;
            }
        }
        return count;
    }

    private HashSet<String> getValidCharacters(CharacterClassType characterClassType,
            ValuePolicyType passwordPolicy) {
        if (null != characterClassType.getValue()) {
            return new HashSet<>(StringPolicyUtils.stringTokenizer(characterClassType.getValue()));
        } else {
            return new HashSet<>(StringPolicyUtils.stringTokenizer(StringPolicyUtils.collectCharacterClass(
                    passwordPolicy.getStringPolicy().getCharacterClass(), characterClassType.getRef())));
        }
    }

    private void testMinimalUniqueCharacters(String password, LimitationsType limitations, OperationResult result,
            List<LocalizableMessage> message) {
        if (limitations.getMinUniqueChars() == null) {
            return;
        }
        HashSet<String> distinctCharacters = new HashSet<>(StringPolicyUtils.stringTokenizer(password));
        if (limitations.getMinUniqueChars() > distinctCharacters.size()) {
            LocalizableMessage msg = new LocalizableMessageBuilder()
                    .key("ValuePolicy.minimalUniqueCharactersNotMet").arg(limitations.getMinUniqueChars())
                    .arg(distinctCharacters.size()).build();
            result.addSubresult(new OperationResult("Check minimal count of unique chars",
                    OperationResultStatus.FATAL_ERROR, msg));
            message.add(msg);
        }
    }

    private void testMinimalLength(String value, LimitationsType limitations, OperationResult result,
            List<LocalizableMessage> messages) {
        if (limitations.getMinLength() == null) {
            return;
        }
        if (value.length() < limitations.getMinLength()) {
            LocalizableMessage msg = new LocalizableMessageBuilder().key("ValuePolicy.minimalSizeNotMet")
                    .arg(limitations.getMinLength()).arg(value.length()).build();
            result.addSubresult(
                    new OperationResult("Check global minimal length", OperationResultStatus.FATAL_ERROR, msg));
            messages.add(msg);
        }
    }

    private void testMaximalLength(String value, LimitationsType limitations, OperationResult result,
            List<LocalizableMessage> messages) {
        if (limitations.getMaxLength() == null) {
            return;
        }
        if (value.length() > limitations.getMaxLength()) {
            LocalizableMessage msg = new LocalizableMessageBuilder().key("ValuePolicy.maximalSizeExceeded")
                    .arg(limitations.getMaxLength()).arg(value.length()).build();
            result.addSubresult(
                    new OperationResult("Check global maximal length", OperationResultStatus.FATAL_ERROR, msg));
            messages.add(msg);
        }
    }

    private void testInvalidCharacters(List<String> valueCharacters, HashSet<String> validChars,
            OperationResult result, List<LocalizableMessage> message) {
        StringBuilder invalidCharacters = new StringBuilder();
        for (String character : valueCharacters) {
            if (!validChars.contains(character)) {
                invalidCharacters.append(character);
            }
        }
        if (invalidCharacters.length() > 0) {
            LocalizableMessage msg = new LocalizableMessageBuilder().key("ValuePolicy.charactersNotAllowed")
                    .arg(invalidCharacters).build();
            result.addSubresult(new OperationResult("Check if value does not contain invalid characters",
                    OperationResultStatus.FATAL_ERROR, msg));
            message.add(msg);
        }
    }

    private <O extends ObjectType> void testCheckExpression(String newPassword, LimitationsType lims,
            AbstractValuePolicyOriginResolver<O> originResolver, String shortDesc, Task task,
            OperationResult result, List<LocalizableMessage> messages)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        for (CheckExpressionType checkExpression : lims.getCheckExpression()) {
            ExpressionType expressionType = checkExpression.getExpression();
            if (expressionType == null) {
                return;
            }
            if (!checkExpression(newPassword, expressionType, originResolver, shortDesc, task, result)) {
                LocalizableMessage msg;
                if (checkExpression.getLocalizableFailureMessage() != null) {
                    msg = LocalizationUtil.toLocalizableMessage(checkExpression.getLocalizableFailureMessage());
                } else if (checkExpression.getFailureMessage() != null) {
                    msg = LocalizableMessageBuilder.buildFallbackMessage(checkExpression.getFailureMessage());
                } else {
                    msg = LocalizableMessageBuilder.buildKey("ValuePolicy.checkExpressionFailed");
                }
                result.addSubresult(
                        new OperationResult("Check expression", OperationResultStatus.FATAL_ERROR, msg));
                messages.add(msg);
            }
        }
    }

    private <O extends ObjectType> void testProhibitedValues(String newPassword,
            ProhibitedValuesType prohibitedValuesType, AbstractValuePolicyOriginResolver<O> originResolver,
            String shortDesc, Task task, OperationResult result, List<LocalizableMessage> messages)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        if (prohibitedValuesType == null || originResolver == null) {
            return;
        }
        Consumer<ProhibitedValueItemType> failAction = (prohibitedItemType) -> {
            LocalizableMessage msg = new LocalizableMessageBuilder().key("ValuePolicy.prohibitedValue").build();
            result.addSubresult(new OperationResult("Prohibited value", OperationResultStatus.FATAL_ERROR, msg));
            messages.add(msg);
        };
        checkProhibitedValues(newPassword, prohibitedValuesType, originResolver, failAction, shortDesc, task,
                result);
    }

    private <O extends ObjectType, R extends ObjectType> boolean checkProhibitedValues(String newPassword,
            ProhibitedValuesType prohibitedValuesType, AbstractValuePolicyOriginResolver<O> originResolver,
            Consumer<ProhibitedValueItemType> failAction, String shortDesc, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {

        if (prohibitedValuesType == null || originResolver == null) {
            return true;
        }

        MutableBoolean isAcceptable = new MutableBoolean(true);
        for (ProhibitedValueItemType prohibitedItemType : prohibitedValuesType.getItem()) {

            ItemPathType itemPathType = prohibitedItemType.getPath();
            if (itemPathType == null) {
                throw new SchemaException("No item path defined in prohibited item in " + shortDesc);
            }
            ItemPath itemPath = itemPathType.getItemPath();

            ResultHandler<R> handler = (object, objectResult) -> {

                PrismProperty<Object> objectProperty = object.findProperty(itemPath);
                if (objectProperty == null) {
                    return true;
                }

                if (isMatching(newPassword, objectProperty)) {
                    if (failAction != null) {
                        failAction.accept(prohibitedItemType);
                    }
                    isAcceptable.setValue(false);
                    return false;
                }

                return true;
            };
            originResolver.resolve(handler, prohibitedItemType, shortDesc, task, result);
        }

        return isAcceptable.booleanValue();
    }

    private boolean isMatching(String newPassword, PrismProperty<Object> objectProperty) {
        for (Object objectRealValue : objectProperty.getRealValues()) {
            if (objectRealValue instanceof String) {
                if (newPassword.equals(objectRealValue)) {
                    return true;
                }
            } else if (objectRealValue instanceof ProtectedStringType) {
                ProtectedStringType newPasswordPs = new ProtectedStringType();
                newPasswordPs.setClearValue(newPassword);
                try {
                    if (protector.compare(newPasswordPs, (ProtectedStringType) objectRealValue)) {
                        return true;
                    }
                } catch (SchemaException | EncryptionException e) {
                    throw new SystemException(e);
                }
            } else {
                if (newPassword.equals(objectRealValue.toString())) {
                    return true;
                }
            }
        }
        return false;
    }

    private String generateAttempt(ValuePolicyType policy, int defaultLength, boolean generateMinimalSize,
            Context ctx, OperationResult result) {

        StringPolicyType stringPolicy = policy.getStringPolicy();
        // if (policy.getLimitations() != null &&
        // policy.getLimitations().getMinLength() != null){
        // generateMinimalSize = true;
        // }
        // setup default values where missing
        // PasswordPolicyUtils.normalize(pp);

        // Optimize usage of limits ass hashmap of limitas and key is set of
        // valid chars for each limitation
        Map<StringLimitType, List<String>> lims = new HashMap<>();
        int minLen = defaultLength;
        int maxLen = defaultLength;
        int unique = defaultLength / 2;
        if (stringPolicy != null) {
            for (StringLimitType l : stringPolicy.getLimitations().getLimit()) {
                if (null != l.getCharacterClass().getValue()) {
                    lims.put(l, StringPolicyUtils.stringTokenizer(l.getCharacterClass().getValue()));
                } else {
                    lims.put(l, StringPolicyUtils.stringTokenizer(StringPolicyUtils.collectCharacterClass(
                            stringPolicy.getCharacterClass(), l.getCharacterClass().getRef())));
                }
            }

            // Get global limitations
            minLen = defaultIfNull(stringPolicy.getLimitations().getMinLength(), 0);
            if (minLen != 0 && minLen > defaultLength) {
                defaultLength = minLen;
            }
            maxLen = defaultIfNull(stringPolicy.getLimitations().getMaxLength(), 0);
            unique = defaultIfNull(stringPolicy.getLimitations().getMinUniqueChars(), minLen);
        }
        // test correctness of definition
        if (unique > minLen) {
            minLen = unique;
            OperationResult reportBug = new OperationResult("Global limitation check");
            reportBug.recordWarning(
                    "There is more required unique characters then defined minimum. Raise minimum to number of required unique chars.");
        }

        if (minLen == 0 && maxLen == 0) {
            minLen = defaultLength;
            maxLen = defaultLength;
            generateMinimalSize = true;
        }

        if (maxLen == 0) {
            if (minLen > defaultLength) {
                maxLen = minLen;
            } else {
                maxLen = defaultLength;
            }
        }

        // Initialize generator
        StringBuilder password = new StringBuilder();

        /*
         * ********************************** Try to find best characters to be
         * first in password
         */
        Map<StringLimitType, List<String>> mustBeFirst = new HashMap<>();
        for (Map.Entry<StringLimitType, List<String>> entry : lims.entrySet()) {
            final StringLimitType key = entry.getKey();
            if (key.isMustBeFirst() != null && key.isMustBeFirst()) {
                mustBeFirst.put(key, entry.getValue());
            }
        }

        // If any limitation was found to be first
        if (!mustBeFirst.isEmpty()) {
            Map<Integer, List<String>> posibleFirstChars = cardinalityCounter(mustBeFirst, null, false, false,
                    result);
            int intersectionCardinality = mustBeFirst.keySet().size();
            List<String> intersectionCharacters = posibleFirstChars.get(intersectionCardinality);
            // If no intersection was found then raise error
            if (null == intersectionCharacters || intersectionCharacters.size() == 0) {
                result.recordFatalError("No intersection for required first character sets in value policy:"
                        + stringPolicy.getDescription());
                // Log error
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error("Unable to generate value for " + ctx.path
                            + ": No intersection for required first character sets in value policy: ["
                            + stringPolicy.getDescription()
                            + "] following character limitation and sets are used:");
                    for (StringLimitType l : mustBeFirst.keySet()) {
                        StrBuilder tmp = new StrBuilder();
                        tmp.appendSeparator(", ");
                        tmp.appendAll(mustBeFirst.get(l));
                        LOGGER.error("L:" + l.getDescription() + " -> [" + tmp + "]");
                    }
                }
                // No more processing unrecoverable conflict
                return null; // EXIT
            } else {
                if (LOGGER.isDebugEnabled()) {
                    StrBuilder tmp = new StrBuilder();
                    tmp.appendSeparator(", ");
                    tmp.appendAll(intersectionCharacters);
                    LOGGER.trace(
                            "Generate first character intersection items [" + tmp + "] into " + ctx.path + ".");
                }
                // Generate random char into password from intersection
                password.append(intersectionCharacters.get(RAND.nextInt(intersectionCharacters.size())));
            }
        }

        /*
         * ************************************** Generate rest to fulfill
         * minimal criteria
         */

        boolean uniquenessReached = false;

        // Count cardinality of elements
        Map<Integer, List<String>> chars;
        for (int i = 0; i < minLen; i++) {

            // Check if still unique chars are needed
            if (password.length() >= unique) {
                uniquenessReached = true;
            }
            // Find all usable characters
            chars = cardinalityCounter(lims, StringPolicyUtils.stringTokenizer(password.toString()), false,
                    uniquenessReached, result);
            // If something goes badly then go out
            if (null == chars) {
                return null;
            }

            if (chars.isEmpty()) {
                LOGGER.trace("Minimal criterias was met. No more characters");
                break;
            }
            // Find lowest possible cardinality and then generate char
            for (int card = 1; card < lims.keySet().size(); card++) {
                if (chars.containsKey(card)) {
                    List<String> validChars = chars.get(card);
                    password.append(validChars.get(RAND.nextInt(validChars.size())));
                    break;
                }
            }
        }

        // test if maximum is not exceeded
        if (password.length() > maxLen) {
            result.recordFatalError(
                    "Unable to meet minimal criteria and not exceed maximal size of " + ctx.path + ".");
            return null;
        }

        /*
         * *************************************** Generate chars to not exceed
         * maximal
         */

        for (int i = 0; i < minLen; i++) {
            // test if max is reached
            if (password.length() == maxLen) {
                // no more characters maximal size is reached
                break;
            }

            if (password.length() >= minLen && generateMinimalSize) {
                // no more characters are needed
                break;
            }

            // Check if still unique chars are needed
            if (password.length() >= unique) {
                uniquenessReached = true;
            }
            // find all usable characters
            chars = cardinalityCounter(lims, StringPolicyUtils.stringTokenizer(password.toString()), true,
                    uniquenessReached, result);

            // If something goes badly then go out
            if (null == chars) {
                // we hope this never happend.
                result.recordFatalError("No valid characters to generate, but no all limitation are reached");
                return null;
            }

            // if selection is empty then no more characters and we can close
            // our work
            if (chars.isEmpty()) {
                if (i == 0) {
                    password.append(RandomStringUtils.randomAlphanumeric(minLen));

                }
                break;
                // if (!StringUtils.isBlank(password.toString()) &&
                // password.length() >= minLen) {
                // break;
                // }
                // check uf this is a firs cycle and if we need to user some
                // default (alphanum) character class.

            }

            // Find lowest possible cardinality and then generate char
            for (int card = 1; card <= lims.keySet().size(); card++) {
                if (chars.containsKey(card)) {
                    List<String> validChars = chars.get(card);
                    password.append(validChars.get(RAND.nextInt(validChars.size())));
                    break;
                }
            }
        }

        if (password.length() < minLen) {
            result.recordFatalError("Unable to generate value for " + ctx.path + " and meet minimal size of "
                    + ctx.path + ". Actual length: " + password.length() + ", required: " + minLen);
            LOGGER.trace("Unable to generate value for " + ctx.path + " and meet minimal size of " + ctx.path
                    + ". Actual length: {}, required: {}", password.length(), minLen);
            return null;
        }

        result.recordSuccess();

        // Shuffle output to solve pattern like output
        StrBuilder sb = new StrBuilder(password.substring(0, 1));
        List<String> shuffleBuffer = StringPolicyUtils.stringTokenizer(password.substring(1));
        Collections.shuffle(shuffleBuffer);
        sb.appendAll(shuffleBuffer);

        return sb.toString();
    }

    private <O extends ObjectType> boolean checkAttempt(String generatedValue, ValuePolicyType policy,
            AbstractValuePolicyOriginResolver<O> originResolver, String shortDesc, Task task,
            OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException,
            CommunicationException, ConfigurationException, SecurityViolationException {
        StringPolicyType stringPolicy = policy.getStringPolicy();
        if (stringPolicy != null) {
            LimitationsType limitationsType = stringPolicy.getLimitations();
            if (limitationsType != null) {
                List<CheckExpressionType> checkExpressionTypes = limitationsType.getCheckExpression();
                if (!checkExpressions(generatedValue, checkExpressionTypes, originResolver, shortDesc, task,
                        result)) {
                    LOGGER.trace("Check expression returned false for generated value in {}", shortDesc);
                    return false;
                }
            }
        }
        if (!checkProhibitedValues(generatedValue, policy.getProhibitedValues(), originResolver, null, shortDesc,
                task, result)) {
            LOGGER.trace("Generated value is prohibited in {}", shortDesc);
            return false;
        }
        // TODO Check pattern
        return true;
    }

    private <O extends ObjectType> boolean checkExpressions(String generatedValue,
            List<CheckExpressionType> checkExpressionTypes, AbstractValuePolicyOriginResolver<O> originResolver,
            String shortDesc, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        for (CheckExpressionType checkExpressionType : checkExpressionTypes) {
            ExpressionType expression = checkExpressionType.getExpression();
            if (!checkExpression(generatedValue, expression, originResolver, shortDesc, task, result)) {
                return false;
            }
        }
        return true;
    }

    private <O extends ObjectType> boolean checkExpression(String generatedValue, ExpressionType checkExpression,
            AbstractValuePolicyOriginResolver<O> originResolver, String shortDesc, Task task,
            OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException,
            CommunicationException, ConfigurationException, SecurityViolationException {
        ExpressionVariables variables = new ExpressionVariables();
        variables.addVariableDefinition(ExpressionConstants.VAR_INPUT, generatedValue);
        variables.addVariableDefinition(ExpressionConstants.VAR_OBJECT,
                originResolver == null ? null : originResolver.getObject());
        PrismPropertyValue<Boolean> output = ExpressionUtil.evaluateCondition(variables, checkExpression,
                expressionFactory, shortDesc, task, result);
        return ExpressionUtil.getBooleanConditionOutput(output);
    }

    /**
     * Count cardinality
     */
    private Map<Integer, List<String>> cardinalityCounter(Map<StringLimitType, List<String>> lims,
            List<String> password, Boolean skipMatchedLims, boolean uniquenessReached, OperationResult op) {
        HashMap<String, Integer> counter = new HashMap<>();

        Map<StringLimitType, List<String>> mustBeFirst = new HashMap<>();
        for (Map.Entry<StringLimitType, List<String>> entry : lims.entrySet()) {
            final StringLimitType key = entry.getKey();
            int counterKey = 1;
            List<String> chars = entry.getValue();
            int i = 0;
            if (null != password) {
                i = charIntersectionCounter(entry.getValue(), password);
            }
            // If max is exceed then error unable to continue
            if (key.getMaxOccurs() != null && i > key.getMaxOccurs()) {
                OperationResult o = new OperationResult("Limitation check :" + key.getDescription());
                o.recordFatalError("Exceeded maximal value for this limitation. " + i + ">" + key.getMaxOccurs());
                op.addSubresult(o);
                return null;
                // if max is all ready reached or skip enabled for minimal skip
                // counting
            } else if (key.getMaxOccurs() != null && i == key.getMaxOccurs()) {
                continue;
                // other cases minimum is not reached
            } else if ((key.getMinOccurs() == null || i >= key.getMinOccurs()) && !skipMatchedLims) {
                continue;
            }
            for (String s : chars) {
                if (null == password || !password.contains(s) || uniquenessReached) {
                    // if (null == counter.get(s)) {
                    counter.put(s, counterKey);
                    // } else {
                    // counter.put(s, counter.get(s) + 1);
                    // }
                }
            }
            counterKey++;
        }

        // If need to remove disabled chars (already reached limitations)
        if (null != password) {
            for (StringLimitType l : lims.keySet()) {
                int i = charIntersectionCounter(lims.get(l), password);
                if (l.getMaxOccurs() != null && i > l.getMaxOccurs()) {
                    OperationResult o = new OperationResult("Limitation check :" + l.getDescription());
                    o.recordFatalError("Exceeded maximal value for this limitation. " + i + ">" + l.getMaxOccurs());
                    op.addSubresult(o);
                    return null;
                } else if (l.getMaxOccurs() != null && i == l.getMaxOccurs()) {
                    // limitation matched remove all used chars
                    LOGGER.trace("Skip " + l.getDescription());
                    for (String charToRemove : lims.get(l)) {
                        counter.remove(charToRemove);
                    }
                }
            }
        }

        // Transpone to better format
        Map<Integer, List<String>> ret = new HashMap<>();
        for (String s : counter.keySet()) {
            // if not there initialize
            ret.computeIfAbsent(counter.get(s), k -> new ArrayList<>());
            ret.get(counter.get(s)).add(s);
        }
        return ret;
    }

    private int charIntersectionCounter(List<String> a, List<String> b) {
        int ret = 0;
        for (String s : b) {
            if (a.contains(s)) {
                ret++;
            }
        }
        return ret;
    }
}