Skip to content

Commit

Permalink
Add whoami to Nessie REST API
Browse files Browse the repository at this point in the history
Basic functionality to retrieve information about the current user, helpful for development purposes.
  • Loading branch information
snazy committed May 27, 2024
1 parent 4a51a69 commit 592d449
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.projectnessie.client.api.ns.ClientSideGetMultipleNamespaces;
import org.projectnessie.client.api.ns.ClientSideGetNamespace;
import org.projectnessie.client.api.ns.ClientSideUpdateNamespace;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.model.Reference;

/**
Expand All @@ -32,6 +33,8 @@
*/
public interface NessieApiV2 extends NessieApiV1 {

NessieUserInfo getUserInfo();

GetRepositoryConfigBuilder getRepositoryConfig();

UpdateRepositoryConfigBuilder updateRepositoryConfig();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.projectnessie.error.NessieNotFoundException;
import org.projectnessie.model.Branch;
import org.projectnessie.model.NessieConfiguration;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.model.Reference;
import org.projectnessie.model.SingleReferenceResponse;

Expand Down Expand Up @@ -67,6 +68,11 @@ public NessieConfiguration getConfig() {
return client.newRequest().path("config").get().readEntity(NessieConfiguration.class);
}

@Override
public NessieUserInfo getUserInfo() {
return client.newRequest().path("config/whoami").get().readEntity(NessieUserInfo.class);
}

@Override
public Branch getDefaultBranch() throws NessieNotFoundException {
return (Branch)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.List;
import org.projectnessie.error.NessieConflictException;
import org.projectnessie.model.NessieConfiguration;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.model.RepositoryConfigResponse;
import org.projectnessie.model.UpdateRepositoryConfigRequest;
import org.projectnessie.model.UpdateRepositoryConfigResponse;
Expand All @@ -35,4 +36,6 @@ public interface ConfigApi {

UpdateRepositoryConfigResponse updateRepositoryConfig(
UpdateRepositoryConfigRequest repositoryConfigUpdate) throws NessieConflictException;

NessieUserInfo getUserInfo();
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.projectnessie.api.v2.ConfigApi;
import org.projectnessie.error.NessieConflictException;
import org.projectnessie.model.NessieConfiguration;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.model.RepositoryConfigResponse;
import org.projectnessie.model.UpdateRepositoryConfigRequest;
import org.projectnessie.model.UpdateRepositoryConfigResponse;
Expand Down Expand Up @@ -123,4 +124,26 @@ RepositoryConfigResponse getRepositoryConfig(
@JsonView(Views.V2.class)
UpdateRepositoryConfigResponse updateRepositoryConfig(
UpdateRepositoryConfigRequest repositoryConfigUpdate) throws NessieConflictException;

@Override
@GET
@jakarta.ws.rs.GET
@Produces(MediaType.APPLICATION_JSON)
@jakarta.ws.rs.Produces(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)
@Operation(summary = "Returns information about the current user.", operationId = "getUserInfo")
@APIResponses({
@APIResponse(
responseCode = "200",
description = "User information",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = NessieUserInfo.class),
examples = {@ExampleObject(ref = "nessieUserInfo")})),
@APIResponse(responseCode = "401", description = "Invalid credentials provided")
})
@JsonView(Views.V2.class)
@Path("whoami")
@jakarta.ws.rs.Path("whoami")
NessieUserInfo getUserInfo();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2024 Dremio
*
* 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.
*/
package org.projectnessie.model;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.List;
import javax.annotation.Nullable;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.immutables.value.Value;

@Schema(
type = SchemaType.OBJECT,
title = "NessieUserInfo",
description = "Information about the current user.")
@Value.Immutable
@JsonSerialize(as = ImmutableNessieUserInfo.class)
@JsonDeserialize(as = ImmutableNessieUserInfo.class)
public interface NessieUserInfo {
@Value.Parameter(order = 1)
boolean anonymous();

@Value.Parameter(order = 2)
@Nullable
@jakarta.annotation.Nullable
String name();

@Value.Parameter(order = 3)
List<String> roles();

static NessieUserInfo nessieUserInfo(boolean anonymous, String name, List<String> roles) {
return null; // ImmutableNessieUserInfo.of(anonymous, name, roles);
}

static Builder builder() {
return ImmutableNessieUserInfo.builder();
}

interface Builder {
Builder anonymous(boolean anonymous);

Builder name(@Nullable String name);

Builder roles(Iterable<String> roles);

Builder addRoles(String role);

Builder addRoles(String... role);

Builder addAllRoles(Iterable<String> role);

NessieUserInfo build();
}
}
9 changes: 9 additions & 0 deletions api/model/src/main/resources/META-INF/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ components:
maxSupportedApiVersion: 2
specVersion: "2.0.0"

nessieUserInfo:
value:
anonymous: false
name: username
roles:
- role_one
- role_two
- role_three

namespace:
value: "a.b.c"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.projectnessie.jaxrs.tests;

import static io.restassured.RestAssured.given;
import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.assertj.core.api.InstanceOfAssertFactories.list;
Expand Down Expand Up @@ -70,6 +71,7 @@
import org.projectnessie.model.ImmutableBranch;
import org.projectnessie.model.ImmutableOperations;
import org.projectnessie.model.Namespace;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.model.Operation.Put;
import org.projectnessie.model.Reference;
import org.projectnessie.model.SingleReferenceResponse;
Expand Down Expand Up @@ -166,6 +168,15 @@ public void testNotFoundUrls(String path) {
rest().head(path).then().statusCode(404);
}

@Test
@NessieApiVersions(versions = {NessieApiVersion.V2})
public void testUserInfo() {
NessieUserInfo userInfo = ((NessieApiV2) this.api()).getUserInfo();
soft.assertThat(userInfo)
.extracting(NessieUserInfo::anonymous, NessieUserInfo::name, NessieUserInfo::roles)
.containsExactly(true, null, emptyList());
}

@Test
@NessieApiVersions(versions = {NessieApiVersion.V1})
public void testReferenceConflictDetailsV1() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.projectnessie.server;

import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

Expand All @@ -23,8 +24,12 @@
import io.quarkus.test.junit.TestProfile;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.projectnessie.client.api.NessieApiV2;
import org.projectnessie.client.auth.BasicAuthenticationProvider;
import org.projectnessie.client.ext.NessieApiVersion;
import org.projectnessie.client.ext.NessieApiVersions;
import org.projectnessie.client.rest.NessieNotAuthorizedException;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.server.authn.AuthenticationEnabledProfile;

@SuppressWarnings("resource") // api() returns an AutoCloseable
Expand All @@ -39,6 +44,17 @@ void testValidCredentials() throws Exception {
assertThat(api().getAllReferences().stream()).isNotEmpty();
}

@Test
@NessieApiVersions(versions = {NessieApiVersion.V2})
public void testUserInfo() {
withClientCustomizer(
c -> c.withAuthentication(BasicAuthenticationProvider.create("test_user", "test_user")));
NessieUserInfo userInfo = ((NessieApiV2) this.api()).getUserInfo();
assertThat(userInfo)
.extracting(NessieUserInfo::anonymous, NessieUserInfo::name, NessieUserInfo::roles)
.containsExactly(false, "test_user", singletonList("test123"));
}

@Test
void testValidAdminCredentials() throws Exception {
withClientCustomizer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Path;
import java.security.Principal;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -27,6 +28,7 @@
import org.projectnessie.model.ImmutableRepositoryConfigResponse;
import org.projectnessie.model.ImmutableUpdateRepositoryConfigResponse;
import org.projectnessie.model.NessieConfiguration;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.model.RepositoryConfig;
import org.projectnessie.model.RepositoryConfigResponse;
import org.projectnessie.model.UpdateRepositoryConfigRequest;
Expand All @@ -45,6 +47,7 @@
public class RestV2ConfigResource implements HttpConfigApi {

private final ConfigApiImpl config;
private final AccessContext accessContext;

// Mandated by CDI 2.0
public RestV2ConfigResource() {
Expand All @@ -54,6 +57,7 @@ public RestV2ConfigResource() {
@Inject
public RestV2ConfigResource(
ServerConfig config, VersionStore store, Authorizer authorizer, AccessContext accessContext) {
this.accessContext = accessContext;
this.config = new ConfigApiImpl(config, store, authorizer, accessContext, 2);
}

Expand Down Expand Up @@ -81,4 +85,24 @@ public UpdateRepositoryConfigResponse updateRepositoryConfig(
.previous(config.updateRepositoryConfig(repositoryConfigUpdate.getConfig()))
.build();
}

@Override
public NessieUserInfo getUserInfo() {
NessieUserInfo.Builder userInfo = NessieUserInfo.builder();

Principal user = accessContext.user();
if (user != null) {
String name = user.getName();
if (name != null && !name.isEmpty()) {
userInfo.name(user.getName());
accessContext.roleIds().forEach(userInfo::addRoles);
userInfo.anonymous(false);
} else {
userInfo.anonymous(true);
}
} else {
userInfo.anonymous(true);
}
return userInfo.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.projectnessie.error.NessieNotFoundException;
import org.projectnessie.model.Branch;
import org.projectnessie.model.NessieConfiguration;
import org.projectnessie.model.NessieUserInfo;
import org.projectnessie.model.Reference;

final class CombinedClientImpl implements NessieApiV2 {
Expand Down Expand Up @@ -151,6 +152,11 @@ public UpdateRepositoryConfigBuilder updateRepositoryConfig() {
return new CombinedUpdateRepositoryConfig(configApi);
}

@Override
public NessieUserInfo getUserInfo() {
return configApi.getUserInfo();
}

@Override
public DeleteReferenceBuilder<Reference> deleteReference() {
return new CombinedDeleteReference(treeApi);
Expand Down

0 comments on commit 592d449

Please sign in to comment.