Skip to content

[Java] Custom headers/properties through Arrow Flight SQL JDBC #33874

@aiguofer

Description

@aiguofer

Describe the usage question you have. Please include as many useful details as possible.

We're currently building a Flight SQL Server and want to use CallHeaderAuthenticator. We want to use token based auth, but the user needs to provide some extra fields (accountId and environmentId). We need to validate the token, and then ensure the user is authorized for the provided accountId and environmentId.

When I pass custom properties to the JDBC URL, they appear in the headers server-side on some requests, but not all.

Here's what we have (CallHeadersWrapper simply creates an object with our custom headers out of passed in headers or a call context).

class CustomBearerTokenAuthenticator(
    private val authClient: AuthClient,
    private val logger: Logger
) :
    CallHeaderAuthenticator {
    private val authException = CallStatus.UNAUTHENTICATED.withDescription("Invalid credentials")

    override fun authenticate(incomingHeaders: CallHeaders?): CallHeaderAuthenticator.AuthResult {
        val bearerToken = AuthUtilities.getValueFromAuthHeader(incomingHeaders, Auth2Constants.BEARER_PREFIX)
            ?: throw authException.toRuntimeException()

        logger.info("Calling auth with headers: ${incomingHeaders.toString()}")
        val customHeaders: CallHeadersWrapper =
            incomingHeaders?.let { CallHeadersWrapper(it, logger) } ?: throw authException.toRuntimeException()

//        if (customHeaders.accountId == null) {
//            throw authException.withDescription("Parameter accountId is required").toRuntimeException()
//        }
//        if (customHeaders.environmentId == null) {
//            throw authException.withDescription("Parameter environmentId is required").toRuntimeException()
//        }
        // FIXME: looks like we don't have access to our custom headers during initial auth... some requests include our custom headers and some don't
        if (authClient.isValid(
                bearerToken,
                customHeaders.accountId ?: "1",
                customHeaders.environmentId ?: "12"
            )
        ) {
            return createAuthResult(bearerToken)
        } else {
            logger.info("Bearer token validation failed.")
            throw authException.toRuntimeException()
        }
    }

    private fun createAuthResult(token: String): CallHeaderAuthenticator.AuthResult {
        class AuthResult : CallHeaderAuthenticator.AuthResult {
            override fun appendToOutgoingHeaders(outgoingHeaders: CallHeaders?) {
                // TODO: we could store other info in headers to track session
                outgoingHeaders?.insert(
                    "authenticated", "yes"
                )
            }

            override fun getPeerIdentity(): String {
                // TODO: peer identity is an identifier for the actor, maybe we want to
                // include other things like accountId and projectId
                return "person"
            }
        }
        return AuthResult()
    }
}

When we use this JDBC connection string: jdbc:arrow-flight-sql://localhost:8811;useEncryption=false;accountId=1;envId=42;token=123 this is the output server side when I establish a connection to the server using DBeaver or DataGrip:

18:29:04.156 [flight-server-default-executor-3] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,grpc-accept-encoding=gzip,authorization=Bearer 123)
18:29:04.161 [flight-server-default-executor-1] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,envid=42,accountid=1,grpc-accept-encoding=gzip,authorization=Bearer 123)
18:29:04.165 [flight-server-default-executor-1] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,grpc-accept-encoding=gzip,authorization=Bearer 123)
18:29:04.170 [flight-server-default-executor-1] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,envid=42,accountid=1,grpc-accept-encoding=gzip,authorization=Bearer 123)
18:29:04.173 [flight-server-default-executor-1] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,grpc-accept-encoding=gzip,authorization=Bearer 123)
18:29:04.178 [flight-server-default-executor-1] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,envid=42,accountid=1,grpc-accept-encoding=gzip,authorization=Bearer 123)
18:29:04.180 [flight-server-default-executor-1] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,grpc-accept-encoding=gzip,authorization=Bearer 123)
18:29:04.183 [flight-server-default-executor-1] INFO  logger -- Calling auth with headers: Metadata(content-type=application/grpc,user-agent=grpc-java-netty/1.49.1,envid=42,accountid=1,grpc-accept-encoding=gzip,authorization=Bearer 123)

Am I misunderstanding something about how the headers are supposed to work here? any help would be greatly appreciated!

Component(s)

Java

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions