/*
 * 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.kafka.common.security.ssl;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class SslPrincipalMapperTest {

    @Test
    public void testValidRules() {
        testValidRule("DEFAULT");
        testValidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/");
        testValidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L, DEFAULT");
        testValidRule("RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/");
        testValidRule("RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/L");
        testValidRule("RULE:^cn=(.?),ou=(.?),dc=(.?),dc=(.?)$/$1@$2/U");

        testValidRule("RULE:^CN=([^,ADEFLTU,]+)(,.*|$)/$1/");
        testValidRule("RULE:^CN=([^,DEFAULT,]+)(,.*|$)/$1/");
    }

    private void testValidRule(String rules) {
        SslPrincipalMapper.fromRules(rules);
    }

    @Test
    public void testInvalidRules() {
        testInvalidRule("default");
        testInvalidRule("DEFAUL");
        testInvalidRule("DEFAULT/L");
        testInvalidRule("DEFAULT/U");

        testInvalidRule("RULE:CN=(.*?),OU=ServiceUsers.*/$1");
        testInvalidRule("rule:^CN=(.*?),OU=ServiceUsers.*$/$1/");
        testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L/U");
        testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/L");
        testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/U");
        testInvalidRule("RULE:^CN=(.*?),OU=ServiceUsers.*$/LU");
    }

    private void testInvalidRule(String rules) {
        assertThrows(IllegalArgumentException.class, () -> SslPrincipalMapper.fromRules(rules));
    }

    @Test
    public void testSslPrincipalMapper() throws Exception {
        String rules = String.join(", ",
            "RULE:^CN=(.*?),OU=ServiceUsers.*$/$1/L",
            "RULE:^CN=(.*?),OU=(.*?),O=(.*?),L=(.*?),ST=(.*?),C=(.*?)$/$1@$2/L",
            "RULE:^cn=(.*?),ou=(.*?),dc=(.*?),dc=(.*?)$/$1@$2/U",
            "RULE:^.*[Cc][Nn]=([a-zA-Z0-9.]*).*$/$1/U",
            "DEFAULT"
        );

        SslPrincipalMapper mapper = SslPrincipalMapper.fromRules(rules);

        assertEquals("duke", mapper.getName("CN=Duke,OU=ServiceUsers,O=Org,C=US"));
        assertEquals("duke@sme", mapper.getName("CN=Duke,OU=SME,O=mycp,L=Fulton,ST=MD,C=US"));
        assertEquals("DUKE@SME", mapper.getName("cn=duke,ou=sme,dc=mycp,dc=com"));
        assertEquals("DUKE", mapper.getName("cN=duke,OU=JavaSoft,O=Sun Microsystems"));
        assertEquals("OU=JavaSoft,O=Sun Microsystems,C=US", mapper.getName("OU=JavaSoft,O=Sun Microsystems,C=US"));
    }

    private void testRulesSplitting(String expected, String rules) {
        SslPrincipalMapper mapper = SslPrincipalMapper.fromRules(rules);
        assertEquals(String.format("SslPrincipalMapper(rules = %s)", expected), mapper.toString());
    }

    @Test
    public void testRulesSplitting() {
        // seeing is believing
        testRulesSplitting("[]", "");
        testRulesSplitting("[DEFAULT]", "DEFAULT");
        testRulesSplitting("[RULE:/]", "RULE://");
        testRulesSplitting("[RULE:/.*]", "RULE:/.*/");
        testRulesSplitting("[RULE:/.*/L]", "RULE:/.*/L");
        testRulesSplitting("[RULE:/, DEFAULT]", "RULE://,DEFAULT");
        testRulesSplitting("[RULE:/, DEFAULT]", "  RULE:// ,  DEFAULT  ");
        testRulesSplitting("[RULE:   /     , DEFAULT]", "  RULE:   /     / ,  DEFAULT  ");
        testRulesSplitting("[RULE:  /     /U, DEFAULT]", "  RULE:  /     /U   ,DEFAULT  ");
        testRulesSplitting("[RULE:([A-Z]*)/$1/U, RULE:([a-z]+)/$1, DEFAULT]", "  RULE:([A-Z]*)/$1/U   ,RULE:([a-z]+)/$1/,   DEFAULT  ");

        // empty rules are ignored
        testRulesSplitting("[]", ",   , , ,      , , ,   ");
        testRulesSplitting("[RULE:/, DEFAULT]", ",,RULE://,,,DEFAULT,,");
        testRulesSplitting("[RULE: /   , DEFAULT]", ",  , RULE: /   /    ,,,   DEFAULT, ,   ");
        testRulesSplitting("[RULE:   /  /U, DEFAULT]", "     ,  , RULE:   /  /U    ,,  ,DEFAULT, ,");

        // escape sequences
        testRulesSplitting("[RULE:\\/\\\\\\(\\)\\n\\t/\\/\\/]", "RULE:\\/\\\\\\(\\)\\n\\t/\\/\\//");
        testRulesSplitting("[RULE:\\**\\/+/*/L, RULE:\\/*\\**/**]", "RULE:\\**\\/+/*/L,RULE:\\/*\\**/**/");

        // rules rule
        testRulesSplitting(
            "[RULE:,RULE:,/,RULE:,\\//U, RULE:,/RULE:,, RULE:,RULE:,/L,RULE:,/L, RULE:, DEFAULT, /DEFAULT, DEFAULT]",
            "RULE:,RULE:,/,RULE:,\\//U,RULE:,/RULE:,/,RULE:,RULE:,/L,RULE:,/L,RULE:, DEFAULT, /DEFAULT/,DEFAULT"
        );
    }

    @Test
    public void testCommaWithWhitespace() throws Exception {
        String rules = "RULE:^CN=((\\\\, *|\\w)+)(,.*|$)/$1/,DEFAULT";

        SslPrincipalMapper mapper = SslPrincipalMapper.fromRules(rules);
        assertEquals("Tkac\\, Adam", mapper.getName("CN=Tkac\\, Adam,OU=ITZ,DC=geodis,DC=cz"));
    }
}
