code: /*
   * Copyright (c) 2019 the Eclipse Milo Authors
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  
  package org.eclipse.milo.opcua.sdk.server.identity;
  
  import java.nio.charset.StandardCharsets;
  import java.security.MessageDigest;
  
  import org.eclipse.milo.opcua.sdk.server.Session;
  import org.eclipse.milo.opcua.stack.core.StatusCodes;
  import org.eclipse.milo.opcua.stack.core.UaException;
  import org.eclipse.milo.opcua.stack.core.security.SecurityAlgorithm;
  import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
  import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
  import org.eclipse.milo.opcua.stack.core.types.structured.AnonymousIdentityToken;
  import org.eclipse.milo.opcua.stack.core.types.structured.SignatureData;
  import org.eclipse.milo.opcua.stack.core.types.structured.UserNameIdentityToken;
  import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
  import org.jetbrains.annotations.Nullable;
  
  public abstract class AbstractUsernameIdentityValidator<T> extends AbstractIdentityValidator<T> {
  
      @Override
      protected T validateAnonymousToken(
          Session session,
          AnonymousIdentityToken token,
          UserTokenPolicy tokenPolicy,
          SignatureData tokenSignature
      ) throws UaException {
  
          return authenticateAnonymousOrThrow(session);
      }
  
      @Override
      protected T validateUsernameToken(
          Session session,
          UserNameIdentityToken token,
          UserTokenPolicy tokenPolicy,
          SignatureData tokenSignature
      ) throws UaException {
  
          String username = token.getUserName();
          ByteString lastNonce = session.getLastNonce();
          int lastNonceLength = lastNonce.length();
  
          if (username == null || username.isEmpty()) {
              throw new UaException(StatusCodes.Bad_IdentityTokenInvalid);
          }
  
          SecurityAlgorithm algorithm;
  
          String algorithmUri = token.getEncryptionAlgorithm();
  
          if (algorithmUri == null || algorithmUri.isEmpty()) {
              SecurityPolicy securityPolicy = session
                  .getSecurityConfiguration().getSecurityPolicy();
  
              algorithm = securityPolicy.getAsymmetricEncryptionAlgorithm();
          } else {
              try {
                  algorithm = SecurityAlgorithm.fromUri(algorithmUri);
              } catch (UaException e) {
                  throw new UaException(StatusCodes.Bad_IdentityTokenInvalid);
              }
  
              if (algorithm != SecurityAlgorithm.Rsa15 &&
                  algorithm != SecurityAlgorithm.RsaOaepSha1 &&
                  algorithm != SecurityAlgorithm.RsaOaepSha256) {
  
                  throw new UaException(StatusCodes.Bad_IdentityTokenInvalid);
              }
          }
  
          byte[] tokenBytes = token.getPassword().bytesOrEmpty();
  
          if (algorithm != SecurityAlgorithm.None) {
              byte[] plainTextBytes = decryptTokenData(session, algorithm, tokenBytes);
  
              //@formatter:off
              long length =
                  ((plainTextBytes[3] & 0xFFL) << 24) |
                  ((plainTextBytes[2] & 0xFFL) << 16) |
                  ((plainTextBytes[1] & 0xFFL) <<  8) |
                  ( plainTextBytes[0] & 0xFFL       );
              //@formatter:on
  
              if (length > plainTextBytes.length - 4) {
                  throw new UaException(StatusCodes.Bad_IdentityTokenInvalid, "invalid token data");
              }
  
              int passwordLength = (int) length - lastNonceLength;
  
              if (passwordLength < 0) {
                  throw new UaException(StatusCodes.Bad_IdentityTokenInvalid, "invalid password length");
              }
  
              if (passwordLength > session.getServer().getConfig().getLimits().getMaxPasswordLength().longValue()) {
                  throw new UaException(StatusCodes.Bad_EncodingLimitsExceeded, "password length exceeds limits");
              }
  
              byte[] passwordBytes = new byte[passwordLength];
              byte[] nonceBytes = new byte[lastNonceLength];
  
              System.arraycopy(plainTextBytes, 4, passwordBytes, 0, passwordBytes.length);
              System.arraycopy(plainTextBytes, 4 + passwordBytes.length, nonceBytes, 0, lastNonceLength);
  
              if (MessageDigest.isEqual(lastNonce.bytes(), nonceBytes)) {
                  String password = new String(passwordBytes, StandardCharsets.UTF_8);
  
                  return authenticateUsernameOrThrow(session, username, password);
              } else {
                  throw new UaException(StatusCodes.Bad_UserAccessDenied);
              }
          } else {
              String password = new String(tokenBytes, StandardCharsets.UTF_8);
  
              return authenticateUsernameOrThrow(session, username, password);
          }
      }
  
      private T authenticateAnonymousOrThrow(Session session) throws UaException {
          T identityObject = authenticateAnonymous(session);
  
          if (identityObject != null) {
              return identityObject;
          } else {
              throw new UaException(StatusCodes.Bad_UserAccessDenied);
          }
      }
  
      private T authenticateUsernameOrThrow(Session session, String username, String password) throws UaException {
          T identityObject = authenticateUsernamePassword(session, username, password);
  
          if (identityObject != null) {
              return identityObject;
          } else {
              throw new UaException(StatusCodes.Bad_UserAccessDenied);
          }
      }
  
      /**
       * Create and return an identity object for an anonymous user.
       *
       * @param session the {@link Session} being activated.
       * @return an identity object of type {@code T} representig an anonymous user, or {@code null} if anonymous
       * authentication is not allowed.
       */
      @Nullable
      protected abstract T authenticateAnonymous(Session session);
  
      /**
       * Authenticate {@code username} with {@code password}, returning an identity object of type {@code T} if the
       * authentication succeeded, or {@code null} if the authentication failed.
       *
       * @param session  the {@link Session} being activated.
       * @param username the username to authenticate.
       * @param password the password to authenticate the user with.
       * @return an identity object of type {@code T} if the authentication succeeded, or {@code null} if it failed.
       */
      @Nullable
      protected abstract T authenticateUsernamePassword(Session session, String username, String password);
  
  }
  