content: /*
   * This file is referred and derived from project apache/tinkerpop
   *
   * https://github.com/apache/tinkerpop/blob/master/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/auth/SimpleAuthenticator.java
   *
   * which has the following license:
   *
   * 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 com.alibaba.graphscope.gremlin.auth;
  
  import static com.google.common.base.Preconditions.checkNotNull;
  
  import static org.apache.commons.lang3.CharUtils.NUL;
  import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_PASSWORD;
  import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_USERNAME;
  
  import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
  import org.apache.tinkerpop.gremlin.server.auth.AuthenticationException;
  import org.apache.tinkerpop.gremlin.server.auth.Authenticator;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.net.InetAddress;
  import java.nio.charset.StandardCharsets;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Map;
  
  public class DefaultAuthenticator implements Authenticator {
      private static final Logger logger = LoggerFactory.getLogger(DefaultAuthenticator.class);
  
      private AuthManager authManager;
  
      @Override
      public boolean requireAuthentication() {
          return authManager.requireAuthentication();
      }
  
      @Override
      public void setup(Map<String, Object> config) {
          this.authManager = checkNotNull(AuthManagerReference.getAuthManager());
      }
  
      @Override
      public final SaslNegotiator newSaslNegotiator(InetAddress remoteAddress) {
          return new PlainTextSaslAuthenticator();
      }
  
      // authenticate the username and the password in credentials
      @Override
      public AuthenticatedUser authenticate(Map<String, String> credentials)
              throws AuthenticationException {
          String username = credentials.get(PROPERTY_USERNAME);
          String password = credentials.get(PROPERTY_PASSWORD);
  
          return authManager.authenticate(username, password);
      }
  
      private class PlainTextSaslAuthenticator implements Authenticator.SaslNegotiator {
          private boolean complete = false;
          private String username;
          private String password;
  
          @Override
          public byte[] evaluateResponse(final byte[] clientResponse) throws AuthenticationException {
              decodeCredentials(clientResponse);
              complete = true;
              return null;
          }
  
          @Override
          public boolean isComplete() {
              return complete;
          }
  
          @Override
          public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException {
              if (!complete) throw new AuthenticationException("SASL negotiation not complete");
              final Map<String, String> credentials = new HashMap<>();
              credentials.put(PROPERTY_USERNAME, username);
              credentials.put(PROPERTY_PASSWORD, password);
              return authenticate(credentials);
          }
  
          /*
           * SASL PLAIN mechanism specifies that credentials are encoded in a
           * sequence of UTF-8 bytes, delimited by 0 (US-ASCII NUL).
           * The form is : {code}authzId<NUL>authnId<NUL>password<NUL>{code}.
           *
           * @param bytes encoded credentials string sent by the client
           */
          private void decodeCredentials(byte[] bytes) throws AuthenticationException {
              byte[] user = null;
              byte[] pass = null;
              int end = bytes.length;
              for (int i = bytes.length - 1; i >= 0; i--) {
                  if (bytes[i] == NUL) {
                      if (pass == null) pass = Arrays.copyOfRange(bytes, i + 1, end);
                      else if (user == null) user = Arrays.copyOfRange(bytes, i + 1, end);
                      end = i;
                  }
              }
  
              if (null == user)
                  throw new AuthenticationException("Authentication ID must not be null");
              if (null == pass) throw new AuthenticationException("Password must not be null");
  
              username = new String(user, StandardCharsets.UTF_8);
              password = new String(pass, StandardCharsets.UTF_8);
          }
      }
  }
  