Skip to content
Draft
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
15 changes: 14 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
* text eol=lf
# Normalize text files to LF, but let Git auto-detect binaries so they are
# stored byte-for-byte (a blanket "* text" corrupts binaries like the Gradle
# wrapper jar by stripping CR bytes during line-ending normalization).
* text=auto eol=lf

# Always treat these as binary regardless of auto-detection.
*.jar binary
*.zip binary
*.gz binary
*.class binary
*.png binary
*.jpg binary
*.gif binary
*.ico binary
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up Java 17
- name: Setup java 25
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'temurin'
distribution: temurin
java-version: '25'

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand Down
34 changes: 17 additions & 17 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ jobs:
os: [ubuntu-latest, windows-latest]

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Check limited Guava usage
if: matrix.os == 'ubuntu-latest'
run: |
if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github "com.google.common.base.Objects" .; then
if grep --with-filename --line-number --no-messages --recursive --exclude-dir=.github --exclude=CLAUDE.md "com.google.common.base.Objects" .; then
echo "Error: use java.util.Objects instead of com.google.common.base.Objects"
exit 1
fi

- name: Setup java 17 for building
- name: Setup java 25 for building
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
java-version: '25'

- name: Set environment variables on Ubuntu
if: matrix.os == 'ubuntu-latest'
Expand All @@ -59,7 +59,7 @@ jobs:
./gradlew.bat build

- name: Setup java ${{ matrix.java-version }} for testing
if: matrix.java-version != '17'
if: matrix.java-version != '25'
uses: actions/setup-java@v5
with:
distribution: temurin
Expand All @@ -69,20 +69,20 @@ jobs:
if: matrix.os == 'ubuntu-latest'
run: |
cd functional
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.11.4/junit-platform-console-standalone-1.11.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.9.8/spotbugs-annotations-4.9.8.jar
javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest.java
java -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest
javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${DEV_VERSION} -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. TestUserAgent
javac -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${RELEASE_VERSION} -cp spotbugs-annotations-4.9.8.jar:junit-platform-console-standalone-1.11.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. TestUserAgent
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.14.4/junit-platform-console-standalone-1.14.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.10.3/spotbugs-annotations-4.10.3.jar
javac -cp spotbugs-annotations-4.10.3.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest.java
java -cp spotbugs-annotations-4.10.3.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:../adminapi/build/libs/minio-admin-${DEV_VERSION}-all.jar:. FunctionalTest
javac -cp spotbugs-annotations-4.10.3.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${DEV_VERSION} -cp spotbugs-annotations-4.10.3.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${DEV_VERSION}-all.jar:. TestUserAgent
javac -cp spotbugs-annotations-4.10.3.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. ./TestUserAgent.java
java -Dversion=${RELEASE_VERSION} -cp spotbugs-annotations-4.10.3.jar:junit-platform-console-standalone-1.14.4.jar:../api/build/libs/minio-${RELEASE_VERSION}-all.jar:. TestUserAgent

- name: Run tests on Windows
if: matrix.os == 'windows-latest'
run: |
cd functional
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.11.4/junit-platform-console-standalone-1.11.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.9.8/spotbugs-annotations-4.9.8.jar
javac -encoding UTF-8 -cp "spotbugs-annotations-4.9.8.jar;junit-platform-console-standalone-1.11.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest.java
java -cp "spotbugs-annotations-4.9.8.jar;junit-platform-console-standalone-1.11.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest
curl -sSfLO https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.14.4/junit-platform-console-standalone-1.14.4.jar
curl -sSfLO https://repo1.maven.org/maven2/com/github/spotbugs/spotbugs-annotations/4.10.3/spotbugs-annotations-4.10.3.jar
javac -encoding UTF-8 -cp "spotbugs-annotations-4.10.3.jar;junit-platform-console-standalone-1.14.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest.java
java -cp "spotbugs-annotations-4.10.3.jar;junit-platform-console-standalone-1.14.4.jar;../api/build/libs/minio-$Env:DEV_VERSION-all.jar;../adminapi/build/libs/minio-admin-$Env:DEV_VERSION-all.jar;." FunctionalTest
4 changes: 3 additions & 1 deletion adminapi/src/main/java/io/minio/admin/Crypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ public static byte[] encrypt(byte[] payload, String password) throws MinioExcept
boolean done = false;
for (int nonceId = 1; !done; nonceId++) {
int to = from + CHUNK_SIZE;
if (to > payload.length) {
// Use >= so a payload that is an exact multiple of CHUNK_SIZE marks its final full chunk as
// the last one, rather than emitting an extra empty trailing chunk (matches madmin-go/sio).
if (to >= payload.length) {
additionalData = markAsLast(additionalData);
to = payload.length;
done = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,25 @@ public long objectsTotalSize() {
}

public Map<String, BucketTargetUsageInfo> objectsReplicationInfo() {
return Collections.unmodifiableMap(this.objectsReplicationInfo);
return this.objectsReplicationInfo == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.objectsReplicationInfo);
}

public long bucketsCount() {
return bucketsCount;
}

public Map<String, BucketUsageInfo> bucketsUsageInfo() {
return Collections.unmodifiableMap(this.bucketsUsageInfo);
return this.bucketsUsageInfo == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.bucketsUsageInfo);
}

public Map<String, Long> bucketsSizes() {
return Collections.unmodifiableMap(bucketsSizes);
return bucketsSizes == null
? Collections.emptyMap()
: Collections.unmodifiableMap(bucketsSizes);
}

public AllTierStats tierStats() {
Expand Down Expand Up @@ -203,7 +209,9 @@ public long objectsCount() {
}

public Map<String, Long> objectsSizesHistogram() {
return Collections.unmodifiableMap(this.objectsSizesHistogram);
return this.objectsSizesHistogram == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.objectsSizesHistogram);
}

public long versionsCount() {
Expand All @@ -215,7 +223,9 @@ public long objectReplicaTotalSize() {
}

public Map<String, BucketTargetUsageInfo> objectsReplicationInfo() {
return Collections.unmodifiableMap(this.objectsReplicationInfo);
return this.objectsReplicationInfo == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.objectsReplicationInfo);
}
}

Expand Down Expand Up @@ -249,7 +259,7 @@ public static class AllTierStats {
private Map<String, TierStats> tiers;

public Map<String, TierStats> tiers() {
return Collections.unmodifiableMap(this.tiers);
return this.tiers == null ? Collections.emptyMap() : Collections.unmodifiableMap(this.tiers);
}
}
}
14 changes: 10 additions & 4 deletions adminapi/src/main/java/io/minio/admin/GetServerInfoResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,9 @@ public String commitID() {
}

public Map<String, String> network() {
return Collections.unmodifiableMap(this.network);
return this.network == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.network);
}

public List<Disk> disks() {
Expand Down Expand Up @@ -321,7 +323,9 @@ public GCStats gCStats() {
}

public Map<String, String> minioEnvVars() {
return Collections.unmodifiableMap(this.minioEnvVars);
return this.minioEnvVars == null
? Collections.emptyMap()
: Collections.unmodifiableMap(this.minioEnvVars);
}

@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down Expand Up @@ -613,11 +617,13 @@ public Integer totalErrorsTimeout() {
}

public Map<String, TimedAction> lastMinute() {
return Collections.unmodifiableMap(lastMinute);
return lastMinute == null
? Collections.emptyMap()
: Collections.unmodifiableMap(lastMinute);
}

public Map<String, String> apiCalls() {
return Collections.unmodifiableMap(apiCalls);
return apiCalls == null ? Collections.emptyMap() : Collections.unmodifiableMap(apiCalls);
}

public Long totalTokens() {
Expand Down
68 changes: 46 additions & 22 deletions adminapi/src/main/java/io/minio/admin/MinioAdminClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,12 @@ private OkHttpClient getHttpClient(PrintWriter traceStream) {
}

private Response httpExecute(
Http.Method method, Command command, Multimap<String, String> queryParamMap, byte[] body)
Http.Method method,
Command command,
Multimap<String, String> queryParamMap,
byte[] body,
Credentials creds)
throws IOException, MinioException {
Credentials creds = getCredentials();

HttpUrl.Builder urlBuilder =
this.baseUrl
.newBuilder()
Expand Down Expand Up @@ -224,7 +226,21 @@ private Response execute(
Http.Method method, Command command, Multimap<String, String> queryParamMap, byte[] body)
throws MinioException {
try {
return httpExecute(method, command, queryParamMap, body);
return httpExecute(method, command, queryParamMap, body, getCredentials());
} catch (IOException e) {
throw new MinioException(e);
}
}

private Response execute(
Http.Method method,
Command command,
Multimap<String, String> queryParamMap,
byte[] body,
Credentials creds)
throws MinioException {
try {
return httpExecute(method, command, queryParamMap, body, creds);
} catch (IOException e) {
throw new MinioException(e);
}
Expand Down Expand Up @@ -258,7 +274,8 @@ public void addUser(
Http.Method.PUT,
Command.ADD_USER,
ImmutableMultimap.of("accessKey", accessKey),
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(userInfo), creds.secretKey()))) {
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(userInfo), creds.secretKey()),
creds)) {
} catch (JsonProcessingException e) {
throw new MinioException(e);
}
Expand Down Expand Up @@ -292,8 +309,8 @@ public UserInfo getUserInfo(String accessKey) throws MinioException {
* @throws MinioException thrown to indicate SDK exception.
*/
public Map<String, UserInfo> listUsers() throws MinioException {
try (Response response = execute(Http.Method.GET, Command.LIST_USERS, null, null)) {
Credentials creds = getCredentials();
Credentials creds = getCredentials();
try (Response response = execute(Http.Method.GET, Command.LIST_USERS, null, null, creds)) {
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
MapType mapType =
OBJECT_MAPPER
Expand Down Expand Up @@ -453,12 +470,14 @@ public long getBucketQuota(String bucketName) throws MinioException {
OBJECT_MAPPER
.getTypeFactory()
.constructMapType(HashMap.class, String.class, JsonNode.class);
return OBJECT_MAPPER.<Map<String, JsonNode>>readValue(response.body().bytes(), mapType)
.entrySet().stream()
return OBJECT_MAPPER
.<Map<String, JsonNode>>readValue(response.body().bytes(), mapType)
.entrySet()
.stream()
.filter(entry -> "quota".equals(entry.getKey()))
.findFirst()
.map(entry -> Long.valueOf(entry.getValue().toString()))
.orElseThrow(() -> new IllegalArgumentException("found not quota"));
.map(entry -> entry.getValue().asLong())
.orElseThrow(() -> new IllegalArgumentException("quota not found in response"));
Comment on lines +473 to +480

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding method and relevant imports in the target file.
file="adminapi/src/main/java/io/minio/admin/MinioAdminClient.java"
wc -l "$file"
sed -n '430,520p' "$file"

# Find any other quota parsing or validation logic in the repository.
rg -n "quota not found in response|asLong\(\)|quota" adminapi/src/main/java/io/minio/admin -S

Repository: minio/minio-java

Length of output: 4603


Parse quota as a number before returning it.

asLong() coerces non-numeric JSON to 0, so a malformed response is indistinguishable from a cleared quota. Treat a non-numeric quota as an error instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@adminapi/src/main/java/io/minio/admin/MinioAdminClient.java` around lines 473
- 480, The quota parsing in MinioAdminClient should reject non-numeric JSON
instead of coercing it to 0. In the response-handling code that streams the
parsed map and extracts the "quota" entry, check that the JsonNode is actually
numeric before converting it, and throw an error if it is missing or not a
number rather than relying on asLong(). Keep the existing quota lookup flow, but
update the parsing logic to treat malformed quota values as failures.

} catch (IOException e) {
throw new MinioException(e);
}
Expand Down Expand Up @@ -674,7 +693,8 @@ public Credentials addServiceAccount(
Http.Method.PUT,
Command.ADD_SERVICE_ACCOUNT,
null,
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) {
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()),
creds)) {
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
return OBJECT_MAPPER.readValue(jsonData, AddServiceAccountResponse.class).credentials();
} catch (JsonProcessingException e) {
Expand All @@ -689,7 +709,7 @@ public Credentials addServiceAccount(
*
* @param accessKey Access key.
* @param newSecretKey New secret key.
* @param newPolicy New policy as JSON string .
* @param newPolicy New policy as JSON string.
* @param newStatus New service account status.
* @param newName New service account name.
* @param newDescription New description.
Expand All @@ -700,7 +720,7 @@ public void updateServiceAccount(
@Nonnull String accessKey,
@Nullable String newSecretKey,
@Nullable Map<String, Object> newPolicy,
@Nullable boolean newStatus,
@Nullable Boolean newStatus,
@Nullable String newName,
@Nullable String newDescription,
@Nullable ZonedDateTime newExpiration)
Expand All @@ -722,7 +742,7 @@ public void updateServiceAccount(
serviceAccount.put("newSecretKey", newSecretKey);
}
if (newPolicy != null && !newPolicy.isEmpty()) serviceAccount.put("newPolicy", newPolicy);
serviceAccount.put("newStatus", newStatus ? "on" : "off");
if (newStatus != null) serviceAccount.put("newStatus", newStatus ? "on" : "off");
if (newName != null && !newName.isEmpty()) serviceAccount.put("newName", newName);
if (newDescription != null && !newDescription.isEmpty()) {
serviceAccount.put("newDescription", newDescription);
Expand All @@ -737,7 +757,8 @@ public void updateServiceAccount(
Http.Method.POST,
Command.UPDATE_SERVICE_ACCOUNT,
ImmutableMultimap.of("accessKey", accessKey),
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()))) {
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(serviceAccount), creds.secretKey()),
creds)) {
} catch (JsonProcessingException e) {
throw new MinioException(e);
}
Expand Down Expand Up @@ -775,13 +796,14 @@ public ListServiceAccountResponse listServiceAccount(@Nonnull String username)
throw new IllegalArgumentException("user name must be provided");
}

Credentials creds = getCredentials();
try (Response response =
execute(
Http.Method.GET,
Command.LIST_SERVICE_ACCOUNTS,
ImmutableMultimap.of("user", username),
null)) {
Credentials creds = getCredentials();
null,
creds)) {
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
return OBJECT_MAPPER.readValue(jsonData, ListServiceAccountResponse.class);
} catch (IOException e) {
Expand All @@ -802,13 +824,14 @@ public GetServiceAccountInfoResponse getServiceAccountInfo(@Nonnull String acces
if (accessKey == null || accessKey.isEmpty()) {
throw new IllegalArgumentException("access key must be provided");
}
Credentials creds = getCredentials();
try (Response response =
execute(
Http.Method.GET,
Command.INFO_SERVICE_ACCOUNT,
ImmutableMultimap.of("accessKey", accessKey),
null)) {
Credentials creds = getCredentials();
null,
creds)) {
byte[] jsonData = Crypto.decrypt(response.body().byteStream(), creds.secretKey());
return OBJECT_MAPPER.readValue(jsonData, GetServiceAccountInfoResponse.class);
} catch (IOException e) {
Expand All @@ -822,7 +845,7 @@ private PolicyAssociationResponse attachDetachPolicy(
@Nullable String user,
@Nullable String group)
throws MinioException {
if (!(user != null ^ group != null)) {
if (!Utils.xor(user, group)) {
throw new IllegalArgumentException("either user or group must be provided");
}

Expand All @@ -840,7 +863,8 @@ private PolicyAssociationResponse attachDetachPolicy(
Http.Method.POST,
command,
null,
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(map), creds.secretKey()))) {
Crypto.encrypt(OBJECT_MAPPER.writeValueAsBytes(map), creds.secretKey()),
creds)) {
return OBJECT_MAPPER.readValue(
Crypto.decrypt(response.body().byteStream(), creds.secretKey()),
PolicyAssociationResponse.class);
Expand Down
2 changes: 1 addition & 1 deletion adminapi/src/main/java/io/minio/admin/Status.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static Status fromString(String statusString) {
return DISABLED;
}

if (statusString.isEmpty()) {
if (statusString == null || statusString.isEmpty()) {
return null;
}

Expand Down
Loading
Loading