content: /*
   * 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.cassandra.auth;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Optional;
  
  import org.apache.cassandra.config.DatabaseDescriptor;
  import org.apache.cassandra.exceptions.InvalidRequestException;
  import org.apache.cassandra.exceptions.SyntaxException;
  import org.apache.cassandra.utils.FBUtilities;
  import org.mindrot.jbcrypt.BCrypt;
  
  public class RoleOptions
  {
      private final Map<IRoleManager.Option, Object> options = new HashMap<>();
  
      /**
       * Set a value for a specific option.
       * Throws SyntaxException if the same option is set multiple times
       * @param option
       * @param value
       */
      public void setOption(IRoleManager.Option option, Object value)
      {
          if (options.containsKey(option))
              throw new SyntaxException(String.format("Multiple definition for property '%s'", option.name()));
          options.put(option, value);
      }
  
      /**
       * Return true if there are no options with values set, false otherwise
       * @return whether any options have values set or not
       */
      public boolean isEmpty()
      {
          return options.isEmpty();
      }
  
      /**
       * Return a map of all the options which have been set
       * @return all options with values
       */
      public Map<IRoleManager.Option, Object> getOptions()
      {
          return options;
      }
  
      /**
       * Return a boolean value of the superuser option
       * @return superuser option value
       */
      public Optional<Boolean> getSuperuser()
      {
          return Optional.ofNullable((Boolean) options.get(IRoleManager.Option.SUPERUSER));
      }
  
      /**
       * Return a boolean value of the login option
       * @return login option value
       */
      public Optional<Boolean> getLogin()
      {
          return Optional.ofNullable((Boolean) options.get(IRoleManager.Option.LOGIN));
      }
  
      /**
       * Return the string value of the password option
       * @return password option value
       */
      public Optional<String> getPassword()
      {
          return Optional.ofNullable((String)options.get(IRoleManager.Option.PASSWORD));
      }
  
      public boolean isGeneratedPassword()
      {
          return (Boolean) options.getOrDefault(IRoleManager.Option.GENERATED_PASSWORD, Boolean.FALSE);
      }
  
      /**
       * Return the string value of the hashed password option.
       * @return hashed password option value
       */
      public Optional<String> getHashedPassword()
      {
          return Optional.ofNullable((String) options.get(IRoleManager.Option.HASHED_PASSWORD));
      }
  
      /**
       * Return a {@code Map<String, String>} representing custom options
       * It is the responsiblity of IRoleManager implementations which support
       * IRoleManager.Option.OPTION to handle type checking and conversion of these
       * values, if present
       * @return map of custom options
       */
      @SuppressWarnings("unchecked")
      public Optional<Map<String, String>> getCustomOptions()
      {
          return Optional.ofNullable((Map<String, String>) options.get(IRoleManager.Option.OPTIONS));
      }
  
      /**
       * Validate the contents of the options in two ways:
       * - Ensure that only a subset of the options supported by the configured IRoleManager are set
       * - Validate the type of any option values present.
       * Should either condition fail, then InvalidRequestException is thrown. This method is called
       * during validation of CQL statements, so the IRE results in a error response to the client.
       *
       * @throws InvalidRequestException if any options which are not supported by the configured IRoleManager
       *     are set or if any option value is of an incorrect type.
       */
      public void validate()
      {
          for (Map.Entry<IRoleManager.Option, Object> option : options.entrySet())
          {
              if (!DatabaseDescriptor.getRoleManager().supportedOptions().contains(option.getKey()))
                  throw new InvalidRequestException(String.format("%s doesn't support %s",
                                                                  DatabaseDescriptor.getRoleManager().getClass().getName(),
                                                                  option.getKey()));
              switch (option.getKey())
              {
                  case LOGIN:
                  case SUPERUSER:
                      if (!(option.getValue() instanceof Boolean))
                          throw new InvalidRequestException(String.format("Invalid value for property '%s'. " +
                                                                          "It must be a boolean",
                                                                          option.getKey()));
                      break;
                  case PASSWORD:
                      if (!(option.getValue() instanceof String))
                          throw new InvalidRequestException(String.format("Invalid value for property '%s'. " +
                                                                          "It must be a string",
                                                                          option.getKey()));
                      if (options.containsKey(IRoleManager.Option.HASHED_PASSWORD))
                          throw new InvalidRequestException(String.format("Properties '%s' and '%s' are mutually exclusive",
                                                                          IRoleManager.Option.PASSWORD, IRoleManager.Option.HASHED_PASSWORD));
                      break;
                  case HASHED_PASSWORD:
                      if (!(option.getValue() instanceof String))
                          throw new InvalidRequestException(String.format("Invalid value for property '%s'. " +
                                                                          "It must be a string",
                                                                          option.getKey()));
                      if (options.containsKey(IRoleManager.Option.PASSWORD))
                          throw new InvalidRequestException(String.format("Properties '%s' and '%s' are mutually exclusive",
                                                                          IRoleManager.Option.PASSWORD, IRoleManager.Option.HASHED_PASSWORD));
                      try
                      {
                          BCrypt.checkpw("dummy", (String) option.getValue());
                      }
                      catch (Exception e)
                      {
                          throw new InvalidRequestException("Invalid hashed password value. Please use jBcrypt.");
                      }
                      break;
                  case GENERATED_PASSWORD:
                      if (options.containsKey(IRoleManager.Option.PASSWORD))
                          throw new InvalidRequestException(String.format("Properties '%s' and '%s' are mutually exclusive",
                                                                          IRoleManager.Option.PASSWORD, IRoleManager.Option.GENERATED_PASSWORD));
                      if (options.containsKey(IRoleManager.Option.HASHED_PASSWORD))
                          throw new InvalidRequestException(String.format("Properties '%s' and '%s' are mutually exclusive",
                                                                          IRoleManager.Option.HASHED_PASSWORD, IRoleManager.Option.GENERATED_PASSWORD));
                      break;
                  case OPTIONS:
                      if (!(option.getValue() instanceof Map))
                          throw new InvalidRequestException(String.format("Invalid value for property '%s'. " +
                                                                          "It must be a map",
                                                                          option.getKey()));
                      break;
  
              }
          }
      }
  
      public String toString()
      {
          return FBUtilities.toString(options);
      }
  }
  