/*
 * 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.
 */

using System;
using Apache.Geode.DUnitFramework;

namespace Apache.Geode.Client.Tests
{
  using Apache.Geode.Client;
  /// <summary>
  /// Enumeration for the various client server operations.
  /// </summary>
  public enum OperationCode
  {
    Get,
    Put,
    GetAll,
    PutAll,
    RemoveAll,
    Destroy,
    Invalidate,
    GetServerKeys,
    RegisterInterest,
    UnregisterInterest,
    Query,
    ExecuteCQ,
    ExecuteFunction,
    RegionDestroy
  }

  /// <summary>
  /// Encapsulates obtaining authorized and unauthorized credentials for a
  /// given operation in a region. Implementations will be for different
  /// kinds of authorization scheme and authentication scheme combos.
  /// </summary>
  public abstract class AuthzCredentialGenerator
  {

    /// <summary>
    /// Enumeration for various <c>AuthzCredentialGenerator</c> implementations.
    /// </summary>
    /// <remarks>
    /// The following schemes are supported as of now:
    /// <list type="number">
    /// <item><description>
    /// <c>DummyAuthorization</c> with <c>DummyAuthenticator</c>
    /// </description></item>
    /// <item><description>
    /// <c>XMLAuthorization</c> with <c>DummyAuthenticator</c></li>
    /// </description></item>
    /// <item><description>
    /// <c>XMLAuthorization</c> with <c>LDAPAuthenticator</c>
    /// </description></item>
    /// <item><description>
    /// <c>XMLAuthorization</c> with <c>PKCSAuthenticator</c>
    /// </description></item>
    /// <item><description>
    /// <c>XMLAuthorization</c> when using SSL sockets
    /// </description></item>
    /// </list>
    /// 
    /// To add a new authorization scheme the following needs to be done:
    /// <list type="number">
    /// <item><description>
    /// Add implementation for <c>AccessControl</c> on the java server.
    /// </description></item>
    /// <item><description>
    /// Choose the authentication schemes that it shall work with, from
    /// <see cref="CredentialGenerator.ClassCode"/>
    /// </description></item>
    /// <item><description>
    /// Add a new enumeration value for the scheme in this enum.
    /// </description></item>
    /// <item><description>
    /// Add an implementation for <c>AuthzCredentialGenerator</c>. Note the
    /// <see cref="AuthzCredentialGenerator.init"/> method where different
    /// authentication schemes can be passed and initialize differently for
    /// the authentication schemes that shall be handled by this authorization
    /// callback.
    /// </description></item>
    /// <item><description>
    /// Modify the <see cref="AuthzCredentialGenerator.create"/> method to add
    /// creation of an instance of the new implementation for the
    /// <c>ClassCode</c> enumeration value.
    /// </description></item>
    /// </list>
    /// All dunit tests will automagically start testing the new
    /// implementation after this.
    /// </remarks>
    public enum ClassCode
    {
      Dummy,
      XML
    }

    /// <summary>
    /// The <see cref="CredentialGenerator"/> being used.
    /// </summary>
    protected CredentialGenerator m_cGen;

    /// <summary>
    /// A set of system properties that should be added to the java server
    /// system properties before using the authorization module.
    /// </summary>
    private Properties<string, string> m_sysProps;

    /// <summary>
    /// A factory method to create a new instance of an
    /// <c>AuthzCredentialGenerator</c>
    /// for the given <c>ClassCode</c>. Caller
    /// is supposed to invoke
    /// <see cref="AuthzCredentialGenerator.init"/> immediately
    /// after obtaining the instance.
    /// </summary>
    /// <param name="classCode">
    /// the <c>ClassCode</c> of the <c>AuthzCredentialGenerator</c>
    /// implementation
    /// </param>
    /// <returns>
    /// an instance of <c>AuthzCredentialGenerator</c> for
    /// the given class code
    /// </returns>
    public static AuthzCredentialGenerator Create(ClassCode classCode)
    {
      switch (classCode)
      {
        case ClassCode.Dummy:
          //return new DummyAuthzCredentialGenerator();
          return null;
        case ClassCode.XML:
          return new XmlAuthzCredentialGenerator();
      }
      return null;
    }

    /// <summary>
    /// Initialize the authorized credential generator.
    /// </summary>
    /// <param name="cGen">
    /// an instance of <see cref="CredentialGenerator"/> of the credential
    /// implementation for which to obtain authorized/unauthorized credentials.
    /// </param>
    /// <returns>
    /// false when the given <c>CredentialGenerator</c> is incompatible with
    /// this authorization module.
    /// </returns>
    public bool Init(CredentialGenerator cGen)
    {
      this.m_cGen = cGen;
      try
      {
        this.m_sysProps = Init();
      }
      catch (Exception)
      {
        return false;
      }
      return true;
    }

    /// <summary>
    /// A set of extra properties that should be added to java server system
    /// properties when not null.
    /// </summary>
    public Properties<string, string> SystemProperties
    {
      get
      {
        return this.m_sysProps;
      }
    }

    /// <summary>
    /// Get the <c>CredentialGenerator</c> being used by this instance.
    /// </summary>
    public CredentialGenerator GetCredentialGenerator()
    {
      return this.m_cGen;
    }

    /// <summary>
    /// The <see cref="ClassCode"/> of the particular implementation.
    /// </summary>
    /// <returns>the <c>ClassCode</c></returns>
    public abstract ClassCode GetClassCode();

    /// <summary>
    /// The name of the <c>AccessControl</c> factory function that should be
    /// used as the authorization module on the java server side.
    /// </summary>
    /// <returns>name of the <c>AccessControl</c> factory function</returns>
    public abstract string AccessControl
    {
      get;
    }

    /// <summary>
    /// Get a set of credentials generated using the given index allowed to
    /// perform the given <see cref="OperationCode"/>s for the given regions.
    /// </summary>
    /// <param name="opCodes">
    /// the list of <see cref="OperationCode"/>s of the operations requiring
    /// authorization; should not be null
    /// </param>
    /// <param name="regionNames">
    /// list of the region names requiring authorization; a value of null
    /// indicates all regions
    /// </param>
    /// <param name="index">
    /// used to generate multiple such credentials by passing different
    /// values for this
    /// </param>
    /// <returns>
    /// the set of credentials authorized to perform the given operation in
    /// the given regions
    /// </returns>
    public Properties<string, string> GetAllowedCredentials(OperationCode[] opCodes,
        string[] regionNames, int index)
    {
      int numTries = GetNumPrincipalTries(opCodes, regionNames);
      if (numTries <= 0)
      {
        numTries = 1;
      }
      for (int tries = 0; tries < numTries; ++tries)
      {
        Properties<string, string> principal = GetAllowedPrincipal(opCodes, regionNames,
            (index + tries) % numTries);
        try
        {
          return this.m_cGen.GetValidCredentials(principal);
        }
        catch (IllegalArgumentException)
        {
        }
      }
      return null;
    }

    /// <summary>
    /// Get a set of credentials generated using the given index not allowed to
    /// perform the given <see cref="OperationCode"/>s for the given regions.
    /// The credentials are required to be valid for authentication.
    /// </summary>
    /// <param name="opCodes">
    /// the <see cref="OperationCode"/>s of the operations requiring
    /// authorization failure; should not be null
    /// </param>
    /// <param name="regionNames">
    /// list of the region names requiring authorization failure;
    /// a value of null indicates all regions
    /// </param>
    /// <param name="index">
    /// used to generate multiple such credentials by passing different
    /// values for this
    /// </param>
    /// <returns>
    /// the set of credentials that are not authorized to perform the given
    /// operation in the given region
    /// </returns>
    public Properties<string, string> GetDisallowedCredentials(OperationCode[] opCodes,
        string[] regionNames, int index)
    {
      // This may not be very correct since we use the value of
      // getNumPrincipalTries() but is used to avoid adding another method.
      // Also something like getNumDisallowedPrincipals() will be normally always
      // infinite, and the number here is just to perform some number of tries
      // before giving up.
      int numTries = GetNumPrincipalTries(opCodes, regionNames);
      if (numTries <= 0)
      {
        numTries = 1;
      }
      for (int tries = 0; tries < numTries; tries++)
      {
        Properties<string, string> principal = GetDisallowedPrincipal(opCodes, regionNames,
            (index + tries) % numTries);
        try
        {
          return this.m_cGen.GetValidCredentials(principal);
        }
        catch (IllegalArgumentException)
        {
        }
      }
      return null;
    }

    /// <summary>
    /// Initialize the authorized credential generator.
    /// </summary>
    /// <returns>
    /// A set of extra properties that should be added to java server
    /// system properties when not null.
    /// </returns>
    /// <exception cref="IllegalArgumentException">
    /// when the <see cref="CredentialGenerator"/> is incompatible with
    /// this authorization module.
    /// </exception>
    protected abstract Properties<string, string> Init();

    /// <summary>
    /// Get the number of tries to be done for obtaining valid credentials for
    /// the given operations in the given region. It is required that
    /// <c>getAllowedPrincipal</c> method returns valid principals for values
    /// of <c>index</c> from 0 through (n-1) where <c>n</c> is the value
    /// returned by this method. It is recommended that the principals so
    /// returned be unique for efficiency.
    /// </summary>
    /// <param name="opCodes">
    /// the <see cref="OperationCode"/>s of the operations requiring
    /// authorization
    /// </param>
    /// <param name="regionNames">
    /// list of the region names requiring authorization; a value of null
    /// indicates all regions
    /// </param>
    /// <returns>
    /// the number of principals allowed to perform the given operation in
    /// the given region
    /// </returns>
    protected abstract int GetNumPrincipalTries(OperationCode[] opCodes,
        string[] regionNames);

    /// <summary>
    /// Get a <c>Principal</c> generated using the given index allowed to
    /// perform the given <see cref="OperationCode"/>s for the given region.
    /// </summary>
    /// <param name="opCodes">
    /// the list of <see cref="OperationCode"/>s of the operations requiring
    /// authorization; should not be null
    /// </param>
    /// <param name="regionNames">
    /// list of the region names requiring authorization; a value of null
    /// indicates all regions
    /// </param>
    /// <param name="index">
    /// used to generate multiple such credentials by passing different
    /// values for this
    /// </param>
    /// <returns>
    /// the <c>Principal</c> authorized to perform the given operation in
    /// the given region
    /// </returns>
    protected abstract Properties<string, string> GetAllowedPrincipal(OperationCode[] opCodes,
        string[] regionNames, int index);

    /// <summary>
    /// Required to be implemented by concrete classes that implement this
    /// abstract class.
    /// </summary>
    /// <param name="opCodes">
    /// the <see cref="OperationCode"/>s of the operations requiring
    /// authorization failure; should not be null
    /// </param>
    /// <param name="regionNames">
    /// list of the region names requiring authorization failure;
    /// a value of null indicates all regions
    /// </param>
    /// <param name="index">
    /// used to generate multiple such credentials by passing different
    /// values for this
    /// </param>
    /// <returns>
    /// a <c>Principal</c> not authorized to perform the given operation
    /// in the given region
    /// </returns>
    protected abstract Properties<string, string> GetDisallowedPrincipal(OperationCode[] opCodes,
        string[] regionNames, int index);
  }

  /// <summary>
  /// Utility class for security generators.
  /// </summary>
  public static class Utility
  {
    /// <summary>
    /// Build the string to be passed to the cacheserver command for the given
    /// security authenticator/accessor properties.
    /// </summary>
    public static string GetServerArgs(string authenticator,
      string accessor, string accessorPP, Properties<string, string> extraProps,
      Properties<string, string> javaProps)
    {
      string args = string.Empty;
      if (authenticator != null && authenticator.Length > 0)
      {
        args += (" --J=-Dgemfire.security-client-authenticator=" + authenticator);
      }
      if (accessor != null && accessor.Length > 0)
      {
        args += (" --J=-Dgemfire.security-client-accessor=" + accessor);
      }
      if (accessorPP != null && accessorPP.Length > 0)
      {
        args += (" --J=-Dgemfire.security-client-accessor-pp=" + accessorPP);
      }
      if (extraProps != null)
      {
        PropertyVisitorGeneric<string, string> visitor =
          delegate(string key, string value)
          {
            args += (" --J=-D" + key.ToString() + '=' + value);
          };
        extraProps.ForEach(visitor);
      }
      if (javaProps != null)
      {
        PropertyVisitorGeneric<string, string> visitor =
          delegate(string key, string value)
          {
            args += (" --J=-D" + key.ToString() + '=' + value);
          };
        javaProps.ForEach(visitor);
      }
      args = args.Trim();
      return args;
    }

    /// <summary>
    /// Get the client system properties.
    /// </summary>
    public static void GetClientProperties(string authInit,
      Properties<string, string> credentials, ref Properties<string, string> props)
    {
      if (authInit != null && authInit.Length > 0)
      {
        if (props == null)
        {
          props = new Properties<string, string>();
        }
        string library;
        string factory;
        const string cppIndicator = "::";
        int libIndex = authInit.IndexOf(cppIndicator);
        if (libIndex > 0)
        {
          library = authInit.Substring(0, libIndex);
          factory = authInit.Substring(libIndex + cppIndicator.Length);
        }
        else
        {
          library = "Apache.Geode.Templates.Cache.Security";
          factory = authInit;
        }
        props.Insert("security-client-auth-library", library);
        props.Insert("security-client-auth-factory", factory);
        if (credentials != null)
        {
          props.AddAll(credentials);
        }
      }
    }
  }
}
