Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions build-logic/src/main/kotlin/authmgr-bundle.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ plugins {
id("authmgr-java-production")
}

// Create configurations to hold the core project's source and javadoc artifacts
// These will be used to copy the core project's source and javadoc jars into this project's
// artifacts
// Create configurations to hold the core and agent projects' source and javadoc artifacts.
// These will be used to copy those projects' source and javadoc jars into this project's artifacts.
val coreSources by
configurations.creating {
isCanBeConsumed = false
Expand All @@ -59,6 +58,28 @@ val coreJavadoc by
}
}

val agentSources by
configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
}
}

val agentJavadoc by
configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.JAVADOC))
}
}

dependencies {
api(project(":authmgr-oauth2-core")) {
// exclude dependencies that are already provided by Iceberg
Expand All @@ -69,6 +90,8 @@ dependencies {
}
coreSources(project(":authmgr-oauth2-core", "sourcesElements"))
coreJavadoc(project(":authmgr-oauth2-core", "javadocElements"))
agentSources(project(":authmgr-oauth2-agent", "sourcesElements"))
agentJavadoc(project(":authmgr-oauth2-agent", "javadocElements"))
}

val shadowJar = tasks.named<ShadowJar>("shadowJar")
Expand Down Expand Up @@ -114,18 +137,20 @@ shadowJar.configure {

tasks.named("assemble").configure { dependsOn("shadowJar") }

// Configure the source jar to copy from the core project's source jar
// Configure the source jar to copy from both the core and agent projects' source jars
tasks.named<Jar>("sourcesJar") {
dependsOn(":authmgr-oauth2-core:sourcesJar")
dependsOn(":authmgr-oauth2-core:sourcesJar", ":authmgr-oauth2-agent:sourcesJar")
duplicatesStrategy = DuplicatesStrategy.INCLUDE // LICENSE files may be duplicated
from({ coreSources.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
from({ agentSources.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
}

// Configure the javadoc jar to copy from the core project's javadoc jar
// Configure the javadoc jar to copy from both the core and agent projects' javadoc jars
tasks.named<Jar>("javadocJar") {
dependsOn(":authmgr-oauth2-core:javadocJar")
dependsOn(":authmgr-oauth2-core:javadocJar", ":authmgr-oauth2-agent:javadocJar")
duplicatesStrategy = DuplicatesStrategy.INCLUDE // LICENSE files may be duplicated
from({ coreJavadoc.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
from({ agentJavadoc.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
}

// Skip the javadoc generation task as we'll copy from the core project
Expand Down
2 changes: 1 addition & 1 deletion build-logic/src/main/kotlin/authmgr-root.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ plugins {
spotless {
java {
target("**/*.java")
googleJavaFormat()
googleJavaFormat("1.25.2")
licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"))
endWithNewline()
}
Expand Down
1 change: 1 addition & 0 deletions gradle/projects.main.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

authmgr-bom=bom
authmgr-immutables=tools/immutables
authmgr-oauth2-agent=oauth2/agent
authmgr-oauth2-core=oauth2/core

# Bundles
Expand Down
210 changes: 210 additions & 0 deletions oauth2/agent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright (C) 2025 Dremio Corporation
*
* Licensed 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.
*/

import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

plugins {
id("authmgr-java-production")
id("authmgr-java-testing")
id("authmgr-maven")
}

description = "OAuth2 Agent for Dremio AuthManager"

ext { set("mavenName", "Auth Manager for Apache Iceberg - OAuth2 - Agent") }

val docs by
configurations.creating {
description = "Dependencies for generating configuration documentation"
isCanBeResolved = true
isCanBeConsumed = false
isVisible = false
}

dependencies {
api(libs.nimbus.oauth2.oidc.sdk) {
exclude(group = "com.github.stephenc.jcip", module = "jcip-annotations")
}
api(libs.nimbus.jose.jwt)

implementation(libs.httpclient5)

implementation(libs.smallrye.config)

// optional, but recommended for advanced cryptography (private_key_jwt client auth, DPoP)
compileOnly(libs.bouncycastle.bcpkix)

implementation(libs.slf4j.api)
implementation(libs.caffeine)

implementation(libs.jakarta.annotation.api)
compileOnly(libs.errorprone.annotations)

compileOnly(project(":authmgr-immutables"))
annotationProcessor(project(":authmgr-immutables", configuration = "processor"))

testFixturesApi(project(":authmgr-oauth2-tests"))

testFixturesApi(platform(libs.junit.bom))
testFixturesApi("org.junit.jupiter:junit-jupiter")
testFixturesApi(libs.junit.pioneer)

testFixturesApi(libs.assertj.core)
testFixturesApi(libs.mockito.core)

testFixturesApi(libs.nimbus.oauth2.oidc.sdk)
testFixturesApi(libs.nimbus.jose.jwt)

testFixturesApi(libs.guava)

testFixturesApi(platform(libs.testcontainers.bom))
testFixturesApi("org.testcontainers:testcontainers")
testFixturesApi("org.testcontainers:testcontainers-junit-jupiter")
testFixturesApi(libs.keycloak.admin.client)
testFixturesApi(libs.testcontainers.keycloak)

testFixturesImplementation(libs.bouncycastle.bcpkix)

// Required to compile expectation classes
testFixturesCompileOnly(libs.mockserver.netty)
testFixturesCompileOnly(libs.mockserver.client.java)

testFixturesCompileOnly(project(":authmgr-immutables"))
testFixturesAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))

testImplementation(libs.mockserver.netty)
testImplementation(libs.mockserver.client.java)

testImplementation(libs.bouncycastle.bcpkix)

testCompileOnly(project(":authmgr-immutables"))
testAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))

intTestCompileOnly(project(":authmgr-immutables"))
intTestAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))

intTestRuntimeOnly(libs.bouncycastle.bcpkix)

longTestCompileOnly(project(":authmgr-immutables"))
longTestAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))

longTestRuntimeOnly(libs.bouncycastle.bcpkix)

docs("com.thoughtworks.qdox:qdox:2.2.0")
}

tasks.named<Test>("test").configure {
configForks(4)
commonTestConfig()
}

tasks.named<Test>("intTest").configure {
configForks(3)
commonTestConfig()
}

val bouncyCastle = configurations.create("bouncyCastle")

dependencies { bouncyCastle(libs.bouncycastle.bcpkix) }

tasks.register<Test>("intTestNoBouncyCastle") {
description = "Runs integration tests without BouncyCastle dependencies"
group = "verification"
configForks(3)
commonTestConfig()
shouldRunAfter("test")
useJUnitPlatform()
testClassesDirs = sourceSets.intTest.get().output.classesDirs
classpath = sourceSets.intTest.get().runtimeClasspath - bouncyCastle
}

tasks.named("check") { dependsOn("intTestNoBouncyCastle") }

tasks.named<Test>("longTest").configure {
configForks(3)
commonTestConfig()
if (System.getProperty("authmgr.it.long.total") != null) {
val total = Duration.parse(System.getProperty("authmgr.it.long.total"))
systemProperty("authmgr.it.long.total", total.toIsoString())
// Add a 10-second safety window to the tests default timeout
systemProperty(
"junit.jupiter.execution.timeout.testable.method.default",
(total + 10.seconds).inWholeSeconds.toString() + " s",
)
}
}

val mockitoAgent = configurations.create("mockitoAgent")

dependencies { mockitoAgent(libs.mockito.core) { isTransitive = false } }

tasks { test { jvmArgs("-javaagent:${mockitoAgent.asPath}") } }

fun Test.configForks(forks: Int) {
if (System.getenv("CI") == null) {
maxParallelForks = forks
}
}

fun Test.commonTestConfig() {
val outputMemoryUsage = System.getProperty("authmgr.test.mockserver.outputMemoryUsage")
if (outputMemoryUsage.toBoolean()) {
val outputDir =
project.layout.buildDirectory.dir("reports/mockserver/${this.name}").get().asFile.absolutePath
outputs.dir(outputDir)
File(outputDir).mkdirs()
systemProperty("authmgr.test.mockserver.memoryUsageCsvDirectory", outputDir)
}
}

sourceSets.create("docs") {
java.srcDir("src/docs/java")
resources.srcDir("src/docs/resources")
compileClasspath += docs
runtimeClasspath += docs
}

tasks.named("processDocsResources", ProcessResources::class) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

tasks.register<JavaExec>("generateDocs") {
group = "documentation"
description = "Generates configuration documentation from OAuth2AgentConfig"
mainClass.set("com.dremio.iceberg.authmgr.oauth2.agent.docs.DocumentationGenerator")
classpath = sourceSets.getByName("docs").runtimeClasspath

val inputFile =
project.file("src/main/java/com/dremio/iceberg/authmgr/oauth2/agent/OAuth2AgentConfig.java")
val outputFile = rootProject.file("docs/configuration.md")

val headerFile = sourceSets.getByName("docs").resources.singleFile
val header = headerFile.readText()

inputs.files(inputFile, headerFile)
outputs.file(outputFile)

args(inputFile.absolutePath, header, outputFile.absolutePath)

doFirst { outputFile.parentFile.mkdirs() }
}

tasks.named("publish") { dependsOn("generateDocs") }

rootProject.tasks.named("spotlessMarkdown") { dependsOn(":authmgr-oauth2-agent:generateDocs") }

rootProject.tasks.named("rat") { dependsOn(":authmgr-oauth2-agent:generateDocs") }
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dremio.iceberg.authmgr.oauth2.docs;
package com.dremio.iceberg.authmgr.oauth2.agent.docs;

import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
Expand All @@ -36,7 +36,7 @@
/**
* Parses the properties from the source code and generates documentation for them.
*
* <p>This generator is mostly intended to parse the `OAuth2Properties` class. The parser relies
* <p>This generator is mostly intended to parse the `OAuth2AgentConfig` class. The parser relies
* heavily on conventions, such as the use of `PREFIX` fields, or fields starting with `DEFAULT_`,
* or the presence of nested classes to structure the properties into sections.
*/
Expand Down Expand Up @@ -76,7 +76,8 @@ public class DocumentationGenerator {
KNOWN_REFS = Map.copyOf(refs);
}

private static final String ROOT_CLASS_NAME = "com.dremio.iceberg.authmgr.oauth2.OAuth2Config";
private static final String ROOT_CLASS_NAME =
"com.dremio.iceberg.authmgr.oauth2.agent.OAuth2AgentConfig";

private final Path rootConfigFile;
private final String header;
Expand Down Expand Up @@ -218,7 +219,7 @@ private String sanitizeDescription(Section section, String text) {
private String resolveReference(Section section, String ref, String text) {
String refTarget = KNOWN_REFS.get(ref);
if (refTarget == null) {
if (ref.equals("OAuth2Config#PREFIX")) {
if (ref.equals("OAuth2AgentConfig#PREFIX")) {
refTarget = rootPrefix;
} else if (ref.startsWith("#")) {
// local ref
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@

import static org.assertj.core.api.Assertions.assertThat;

import com.dremio.iceberg.authmgr.oauth2.flow.TokensResult;
import com.dremio.iceberg.authmgr.oauth2.test.ImmutableTestEnvironment.Builder;
import com.dremio.iceberg.authmgr.oauth2.test.TestEnvironment;
import com.dremio.iceberg.authmgr.oauth2.test.junit.EnumLike;
import com.dremio.iceberg.authmgr.oauth2.agent.flow.TokensResult;
import com.dremio.iceberg.authmgr.oauth2.agent.junit.EnumLike;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
Expand Down Expand Up @@ -109,7 +107,7 @@ private void introspectToken(AccessToken accessToken, TestEnvironment env) throw
assertThat(claims.getStringClaim("scope")).contains("catalog");
}

private static Builder envBuilder(
private static ImmutableTestEnvironment.Builder envBuilder(
GrantType initialGrantType, ClientAuthenticationMethod authenticationMethod) {

URI issuerUrl = URI.create(System.getenv(AUTH0_DOMAIN_ENV));
Expand All @@ -119,7 +117,7 @@ private static Builder envBuilder(
? new Scope("catalog")
: new Scope("catalog", "offline_access"); // request refresh token

return TestEnvironment.builder()
return ImmutableTestEnvironment.builder()
.serverRootUrl(issuerUrl)
.authorizationServerUrl(issuerUrl)
.grantType(initialGrantType)
Expand Down
Loading
Loading