@Override
  public String authorize(
      Subject subject,
      InetSocketAddress localAddress,
      InetSocketAddress remoteAddress,
      String path,
      Map<String, String> opaque,
      int request,
      FilePerm mode)
      throws XrootdException {
    if (path == null) {
      throw new IllegalArgumentException("The lfn string must not be null.");
    }

    String authzTokenString = opaque.get("authz");
    if (authzTokenString == null) {
      if (request == XrootdProtocol.kXR_stat
          || request == XrootdProtocol.kXR_statx
          || request == XrootdProtocol.kXR_dirlist
          || request == XrootdProtocol.kXR_locate) {
        return path;
      }
      throw new XrootdException(
          kXR_NotAuthorized, "An authorization token is required for this request.");
    }

    // get the VO-specific keypair or the default keypair if VO
    // was not specified
    KeyPair keypair = getKeys(opaque.get("vo"));

    // decode the envelope from the token using the keypair
    // (Remote public key, local private key)
    Envelope env;
    try {
      env = decodeEnvelope(authzTokenString, keypair);
    } catch (CorruptedEnvelopeException
        | IllegalBlockSizeException
        | NoSuchPaddingException
        | InvalidKeyException
        | BadPaddingException
        | SignatureException e) {
      throw new XrootdException(
          kXR_ArgInvalid, "Error parsing authorization token: " + e.getMessage());
    } catch (NoSuchAlgorithmException
        | NoSuchProviderException
        | InvalidAlgorithmParameterException e) {
      throw new XrootdException(
          kXR_ServerError, "Error parsing authorization token: " + e.getMessage());
    } catch (CredentialException e) {
      throw new XrootdException(
          kXR_NotAuthorized, "Error parsing authorization token: " + e.getMessage());
    }

    // loop through all files contained in the envelope and find
    // the one with the matching lfn if no match is found, the
    // token/envelope is possibly hijacked
    Envelope.GridFile file = findFile(path, env);
    if (file == null) {
      throw new XrootdException(
          kXR_NotAuthorized,
          "Authorization token doesn't contain any file permissions for lfn " + path + ".");
    }

    // check for hostname:port in the TURL. Must match the current
    // xrootd service endpoint.  If this check fails, the token is
    // possibly hijacked
    try {
      if (!Arrays.asList(InetAddress.getAllByName(file.getTurl().getHost()))
          .contains(localAddress.getAddress())) {
        throw new XrootdException(
            kXR_NotAuthorized,
            "Hostname mismatch in authorization token (address="
                + localAddress
                + " turl="
                + file.getTurl()
                + ").");
      }
    } catch (UnknownHostException e) {
      throw new XrootdException(
          kXR_NotAuthorized,
          "Hostname in authorization token is not resolvable (turl=" + file.getTurl() + ").");
    }

    int turlPort = file.getTurl().getPort();
    if (turlPort == -1) {
      turlPort = XrootdProtocol.DEFAULT_PORT;
    }
    if (turlPort != localAddress.getPort()) {
      throw new XrootdException(
          kXR_NotAuthorized,
          "Port mismatch in authorization token (address="
              + localAddress
              + " turl="
              + file.getTurl()
              + ").");
    }

    // the authorization check. read access (lowest permission
    // required) is granted by default (file.getAccess() == 0), we
    // must check only in case of writing
    FilePerm grantedPermission = file.getAccess();
    if (mode == FilePerm.WRITE) {
      if (grantedPermission.ordinal() < FilePerm.WRITE_ONCE.ordinal()) {
        throw new XrootdException(
            kXR_NotAuthorized, "Token lacks authorization for requested operation.");
      }
    } else if (mode == FilePerm.DELETE) {
      if (grantedPermission.ordinal() < FilePerm.DELETE.ordinal()) {
        throw new XrootdException(
            kXR_NotAuthorized, "Token lacks authorization for requested operation.");
      }
    }

    return file.getTurl().getPath();
  }