/* ******************************************************************************
 * Copyright (c) 2020 Stephane Bastian
 *
 * This program and the accompanying materials are made available under the 2
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 3
 *
 * Contributors: 4
 *   Stephane Bastian - initial API and implementation
 ********************************************************************************/
package io.vertx.ext.auth.authentication;

import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;

import java.nio.charset.StandardCharsets;

import static io.vertx.ext.auth.impl.Codec.base64Encode;

/**
 * Credentials used by any {@link AuthenticationProvider} that requires tokens, for example JWT, Oauth2, OpenId Connect
 *
 * @author Paulo Lopes
 */
@DataObject(generateConverter = true, publicConverter = false)
public class UsernamePasswordCredentials implements Credentials {

  private String password;
  private String username;

  protected UsernamePasswordCredentials() {
  }

  public UsernamePasswordCredentials(String username, String password) {
    setUsername(username);
    setPassword(password);
  }

  public UsernamePasswordCredentials(JsonObject jsonObject) {
    UsernamePasswordCredentialsConverter.fromJson(jsonObject, this);
  }

  public String getPassword() {
    return password;
  }

  public String getUsername() {
    return username;
  }

  public UsernamePasswordCredentials setPassword(String password) {
    this.password = password;
    return this;
  }

  public UsernamePasswordCredentials setUsername(String username) {
    this.username = username;
    return this;
  }

  @Override
  public <V> void checkValid(V arg) throws CredentialValidationException {
    if (username == null || username.length() == 0) {
      throw new CredentialValidationException("username cannot be null or empty");
    }
    // passwords are allowed to be empty
    // for example this is used by basic auth
    if (password == null) {
      throw new CredentialValidationException("password cannot be null");
    }
  }

  public JsonObject toJson() {
    JsonObject result = new JsonObject();
    UsernamePasswordCredentialsConverter.toJson(this, result);
    return result;
  }

  @Override
  public String toString() {
    return toJson().encode();
  }

  @Override
  public UsernamePasswordCredentials applyHttpChallenge(String challenge, HttpMethod method, String uri, Integer nc, String cnonce) throws CredentialValidationException {
    if (challenge != null) {
      int spc = challenge.indexOf(' ');

      if (!"Basic".equalsIgnoreCase(challenge.substring(0, spc))) {
        throw new IllegalArgumentException("Only 'Basic' auth-scheme is supported");
      }
    }
    // validate
    checkValid(null);
    return this;
  }

  @Override
  public String toHttpAuthorization() {

    String credentials =
      (username == null ? "" : username) +
        ":" +
        (password == null ? "" : password);

    return "Basic " + base64Encode(credentials.getBytes(StandardCharsets.UTF_8));
  }

}

@@ -1898,34 +1898,8 @@
 null
- %7C%7C username.length() == 0
 ) %7B%0A
@@ -1972,17 +1972,8 @@
 null
- or empty
 %22);%0A
@@ -2950,39 +2950,65 @@
 ) %7B%0A
-%0A
     
-String credentials =
+final StringBuilder sb = new StringBuilder();%0A
 %0A    
- 
+if
  (us
@@ -3018,57 +3018,224 @@
 ame 
-=
+!
 = null
- ? %22%22 : username) +%0A        %22:%22 +
+) %7B%0A      // RFC check%0A      if (username.indexOf(':') != -1) %7B%0A        throw new IllegalArgumentException(%22Username cannot contain ':'%22);%0A      %7D%0A      sb.append(username);
 %0A    
+%7D%0A%0A
     
+sb.append(':');%0A%0A    if 
 (pas
@@ -3244,23 +3244,35 @@
 ord 
-=
+!
 = null
- ? %22%22 : 
+) %7B%0A      sb.append(
 pass
@@ -3277,16 +3277,22 @@
 ssword);
+%0A    %7D
 %0A%0A    re
@@ -3324,19 +3324,21 @@
 ode(
-credentials
+sb.toString()
 .get

Fix #573: allow empty as username