/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.jboss.installer.postinstall.task.impl.secdom;

import org.jboss.dmr.ModelNode;
import org.jboss.installer.postinstall.server.EmbeddedServer;
import org.jboss.installer.postinstall.task.secdom.KerberosConfig;

import java.util.ArrayList;
import java.util.List;

import static org.jboss.as.cli.Util.DEFAULT_PERMISSION_MAPPER;
import static org.jboss.as.cli.Util.ELYTRON;
import static org.jboss.as.cli.Util.FILESYSTEM_REALM;
import static org.jboss.as.cli.Util.NAME;
import static org.jboss.as.cli.Util.PERMISSION_MAPPER;
import static org.jboss.as.cli.Util.REALMS;
import static org.jboss.as.cli.Util.ROLE_DECODER;
import static org.jboss.as.cli.Util.SECURITY_DOMAIN;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.ADD;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.ATTRIBUTE;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.HOST;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.IDENTITY;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.PRIMARY;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.REALM;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.SYSTEM_PROPERTY;
import static org.jboss.installer.postinstall.task.utils.ModelDescriptionConstants.VALUE;
import static org.jboss.installer.postinstall.task.utils.ModelUtils.createEmptyOperation;
import static org.jboss.installer.postinstall.task.utils.ModelUtils.pathAddress;

public class KerberosSecurity {

    public List<ModelNode> toOperations(EmbeddedServer server, String domainName, KerberosConfig config) {

        final String kerberosSecurityFactoryName = config.getKerberosSecurityFactoryName();
        final String principal = config.getPrincipal();
        final String[] mechanismNames = config.getMechanismNames();
        final String[] mechanismOids = config.getMechanismOids();
        final String fileSystemRealmName = config.getFileSystemRealmName();
        final String identityName = config.getIdentityName();
        final String[] identityAttributeValue = config.getIdentityAttributeValue();
        final String simpleRoleDecoderName = config.getSimpleRoleDecoderName();

        final List<ModelNode> ops = new ArrayList<>();

        // 1. Configure a kerberos-security-factory.
        // standalone: /subsystem=elytron/kerberos-security-factory=krbSF:add(principal="HTTP/host@REALM",path="/path/to/http.keytab",mechanism-oids=[1.2.840.113554.1.2.2,1.3.6.1.5.5.2])
        // domain: /profile=*/subsystem=elytron/kerberos-security-factory=krbSF:add(principal="HTTP/host@REALM",path="/path/to/http.keytab",mechanism-oids=[1.2.840.113554.1.2.2,1.3.6.1.5.5.2])
        final ModelNode addKSFOp = createEmptyOperation(ADD, pathAddress(SUBSYSTEM, ELYTRON)
                .add("kerberos-security-factory", kerberosSecurityFactoryName));
        addKSFOp.get("principal").set(principal);
        server.relativise(config.getKeyTabPath(), addKSFOp);
        if (mechanismNames.length > 0) {
            ModelNode list = new ModelNode();
            for (String mechanismName : mechanismNames) {
                list.add(mechanismName);
            }
            addKSFOp.get("mechanism-names").set(list);
        }
        if (mechanismOids.length > 0) {
            ModelNode list = new ModelNode();
            for (String mechanismOid : mechanismOids) {
                list.add(mechanismOid);
            }
            addKSFOp.get("mechanism-oids").set(list);
        }
        ops.add(addKSFOp);

        // 2. Configure the system properties for Kerberos.
        // standalone: /system-property=java.security.krb5.conf:add(value="/path/to/krb5.conf")
        // domain: /host=primary/server-config=*/system-property=java.security.krb5.conf:add(value="/path/to/krb5.conf")
        final ModelNode addKrb5ConfOp = server.isDomain() ? createEmptyOperation(ADD, pathAddress(HOST, PRIMARY).add(SYSTEM_PROPERTY, "java.security.krb5.conf")) : createEmptyOperation(ADD, pathAddress(SYSTEM_PROPERTY, "java.security.krb5.conf"));
        addKrb5ConfOp.get(VALUE).set(config.getKrb5Conf().toString());
        ops.add(addKrb5ConfOp);

        // Configure an Elytron security realm for assigning roles.
        // standalone:
        // /subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.server.config.dir)
        // /subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity(identity=user1@REALM)
        // /subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity-attribute(identity=user1@REALM,name=Roles,value=["Admin","Guest"])
        // domain mode:
        // /profile=*/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.domain.config.dir)
        // /host=primary/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.domain.config.dir)
        // /host=primary/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity(identity=user1@REALM)
        // /host=primary/subsystem=elytron/filesystem-realm=exampleFsRealm:add-identity-attribute(identity=user1@REALM,name=Roles,value=["Admin","Guest"])

        final ModelNode addFilesystemRealmOp = createEmptyOperation(ADD, pathAddress(SUBSYSTEM, ELYTRON)
                .add(FILESYSTEM_REALM, fileSystemRealmName));
        if (server.isDomain()) {
            server.relativise(config.getFileSystemRealmDomainPath(), addFilesystemRealmOp);
        } else {
            server.relativise(config.getFileSystemRealmStandalonePath(), addFilesystemRealmOp);
        }
        ops.add(addFilesystemRealmOp);
        if (server.isDomain()) {
            // /host=primary/subsystem=elytron/filesystem-realm=exampleFsRealm:add(path=fs-realm-users,relative-to=jboss.domain.config.dir)
            // define the realm on the HC with same realm file location, because we need to call runtime operations add-identity and add-identity-attribute from embed-host-controller.
            final ModelNode addFilesystemRealmOnHCOp = createEmptyOperation(ADD, pathAddress(HOST, PRIMARY).add(SUBSYSTEM, ELYTRON)
                    .add(FILESYSTEM_REALM, fileSystemRealmName));
            server.relativise(config.getFileSystemRealmDomainPath(), addFilesystemRealmOnHCOp);
            ops.add(addFilesystemRealmOnHCOp);
        }
        final ModelNode addIdentityOp = server.isDomain() ? createEmptyOperation("add-identity", pathAddress(HOST, PRIMARY).add(SUBSYSTEM, ELYTRON)
                .add(FILESYSTEM_REALM, fileSystemRealmName)) : createEmptyOperation("add-identity", pathAddress(SUBSYSTEM, ELYTRON)
                .add(FILESYSTEM_REALM, fileSystemRealmName));
        addIdentityOp.get(IDENTITY).set(identityName);
        ops.add(addIdentityOp);
        final ModelNode addIdentityAttributeOp = server.isDomain() ? createEmptyOperation("add-identity-attribute", pathAddress(HOST, PRIMARY).add(SUBSYSTEM, ELYTRON)
                .add(FILESYSTEM_REALM, fileSystemRealmName)) : createEmptyOperation("add-identity-attribute", pathAddress(SUBSYSTEM, ELYTRON)
                .add(FILESYSTEM_REALM, fileSystemRealmName));
        addIdentityAttributeOp.get(IDENTITY).set(identityName);
        addIdentityAttributeOp.get(NAME).set(config.getIdentityAttributeName());
        ModelNode list = new ModelNode();
        for (String s : identityAttributeValue) {
            list.add(s);
        }
        addIdentityAttributeOp.get(VALUE).set(list);
        ops.add(addIdentityAttributeOp);

        // 3. Add a simple-role-decoder.
        // standalone: /subsystem=elytron/simple-role-decoder=from-roles-attribute:add(attribute=Roles)
        // domain: /profile=*/subsystem=elytron/simple-role-decoder=from-roles-attribute:add(attribute=Roles)
        final ModelNode addSimpleRoleDecoderOp = createEmptyOperation(ADD, pathAddress(SUBSYSTEM, ELYTRON)
                .add("simple-role-decoder", simpleRoleDecoderName));
        addSimpleRoleDecoderOp.get(ATTRIBUTE).set(config.getSimpleRoleDecoderAttribute());
        ops.add(addSimpleRoleDecoderOp);

        // 4. Configure a security-domain.
        // standalone: /subsystem=elytron/security-domain=exampleFsSD:add(realms=[{realm=exampleFsRealm,role-decoder=from-roles-attribute}],default-realm=exampleFsRealm,permission-mapper=default-permission-mapper)
        // domain: /profile=*/subsystem=elytron/security-domain=exampleFsSD:add(realms=[{realm=exampleFsRealm,role-decoder=from-roles-attribute}],default-realm=exampleFsRealm,permission-mapper=default-permission-mapper)

        final ModelNode addSecurityDomainOp = createEmptyOperation(ADD, pathAddress(SUBSYSTEM, ELYTRON)
                .add(SECURITY_DOMAIN, domainName));
        final ModelNode realm = new ModelNode();
        realm.get(REALM).set(fileSystemRealmName);
        realm.get(ROLE_DECODER).set(simpleRoleDecoderName);
        final ModelNode realms = new ModelNode();
        realms.add(realm);
        addSecurityDomainOp.get(REALMS).set(realms);
        addSecurityDomainOp.get("default-realm").set(fileSystemRealmName);
        addSecurityDomainOp.get(PERMISSION_MAPPER).set(DEFAULT_PERMISSION_MAPPER);
        ops.add(addSecurityDomainOp);

        return ops;
    }

}
