Skip to content

Commit a148374

Browse files
committed
user_view endpoint
1 parent 7f74db7 commit a148374

File tree

8 files changed

+104
-2
lines changed

8 files changed

+104
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ tags
1818
.img/*
1919
connectivity-report.json
2020
*.local
21+
CLAUDE.md

nexus/external-api/output/nexus_tags.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ policy_update PUT /v1/policy
159159
policy_view GET /v1/policy
160160
user_list GET /v1/users
161161
user_logout POST /v1/users/{user_id}/logout
162+
user_view GET /v1/users/{user_id}
162163
utilization_view GET /v1/utilization
163164

164165
API operations found with tag "snapshots"

nexus/external-api/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,6 +3008,17 @@ pub trait NexusExternalApi {
30083008
query_params: Query<PaginatedById<params::OptionalGroupSelector>>,
30093009
) -> Result<HttpResponseOk<ResultsPage<views::User>>, HttpError>;
30103010

3011+
/// Fetch user
3012+
#[endpoint {
3013+
method = GET,
3014+
path = "/v1/users/{user_id}",
3015+
tags = ["silos"],
3016+
}]
3017+
async fn user_view(
3018+
rqctx: RequestContext<Self::Context>,
3019+
path_params: Path<params::UserPath>,
3020+
) -> Result<HttpResponseOk<views::User>, HttpError>;
3021+
30113022
/// Expire all of user's tokens and sessions
30123023
#[endpoint {
30133024
method = POST,

nexus/src/app/silo.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ impl super::Nexus {
313313
Ok(db_silo_user)
314314
}
315315

316-
/// Fetch a user in a Silo
316+
/// Delete all of user's tokens and sessions
317317
pub(crate) async fn current_silo_user_logout(
318318
&self,
319319
opctx: &OpContext,
@@ -338,6 +338,21 @@ impl super::Nexus {
338338
Ok(())
339339
}
340340

341+
/// Fetch a user in a Silo
342+
pub(crate) async fn current_silo_user_lookup(
343+
&self,
344+
opctx: &OpContext,
345+
silo_user_id: Uuid,
346+
) -> LookupResult<(authz::SiloUser, db::model::SiloUser)> {
347+
let (_, authz_silo_user, db_silo_user) =
348+
LookupPath::new(opctx, self.datastore())
349+
.silo_user_id(silo_user_id)
350+
.fetch_for(authz::Action::Read)
351+
.await?;
352+
353+
Ok((authz_silo_user, db_silo_user))
354+
}
355+
341356
// The "local" identity provider (available only in `LocalOnly` Silos)
342357

343358
/// Helper function for looking up a LocalOnly Silo by name

nexus/src/external_api/http_entrypoints.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6705,6 +6705,28 @@ impl NexusExternalApi for NexusExternalApiImpl {
67056705
.await
67066706
}
67076707

6708+
async fn user_view(
6709+
rqctx: RequestContext<Self::Context>,
6710+
path_params: Path<params::UserPath>,
6711+
) -> Result<HttpResponseOk<views::User>, HttpError> {
6712+
let apictx = rqctx.context();
6713+
let handler = async {
6714+
let nexus = &apictx.context.nexus;
6715+
let path = path_params.into_inner();
6716+
let opctx =
6717+
crate::context::op_context_for_external_api(&rqctx).await?;
6718+
let (.., user) = nexus
6719+
.current_silo_user_lookup(&opctx, path.user_id)
6720+
.await?;
6721+
Ok(HttpResponseOk(user.into()))
6722+
};
6723+
apictx
6724+
.context
6725+
.external_latencies
6726+
.instrument_dropshot_handler(&rqctx, handler)
6727+
.await
6728+
}
6729+
67086730
async fn user_logout(
67096731
rqctx: RequestContext<Self::Context>,
67106732
path_params: Path<params::UserPath>,

nexus/tests/integration_tests/endpoints.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ pub static DEMO_SILO_USERS_LIST_URL: LazyLock<String> = LazyLock::new(|| {
138138
pub static DEMO_SILO_USER_ID_GET_URL: LazyLock<String> = LazyLock::new(|| {
139139
format!("/v1/system/users/{{id}}?silo={}", DEFAULT_SILO.identity().name,)
140140
});
141+
pub static DEMO_SILO_USER_ID_IN_SILO_URL: LazyLock<String> =
142+
LazyLock::new(|| "/v1/users/{id}".to_string());
143+
pub static DEMO_SILO_USER_LOGOUT_URL: LazyLock<String> =
144+
LazyLock::new(|| "/v1/users/{id}/logout".to_string());
145+
141146
pub static DEMO_SILO_USER_ID_DELETE_URL: LazyLock<String> =
142147
LazyLock::new(|| {
143148
format!(
@@ -1657,8 +1662,14 @@ pub static VERIFY_ENDPOINTS: LazyLock<Vec<VerifyEndpoint>> =
16571662
allowed_methods: vec![AllowedMethod::Get],
16581663
},
16591664
VerifyEndpoint {
1660-
url: "/v1/users/af47bf12-2eab-4892-8a2f-c064a812c884/logout",
1665+
url: &DEMO_SILO_USER_ID_IN_SILO_URL,
16611666
visibility: Visibility::Protected,
1667+
unprivileged_access: UnprivilegedAccess::ReadOnly,
1668+
allowed_methods: vec![AllowedMethod::Get],
1669+
},
1670+
VerifyEndpoint {
1671+
url: &DEMO_SILO_USER_LOGOUT_URL,
1672+
visibility: Visibility::Public,
16621673
unprivileged_access: UnprivilegedAccess::None,
16631674
allowed_methods: vec![AllowedMethod::Post(serde_json::json!(
16641675
{}

nexus/tests/integration_tests/unauthorized.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ static SETUP_REQUESTS: LazyLock<Vec<SetupReq>> = LazyLock::new(|| {
224224
&*DEMO_SILO_USER_ID_GET_URL,
225225
&*DEMO_SILO_USER_ID_DELETE_URL,
226226
&*DEMO_SILO_USER_ID_SET_PASSWORD_URL,
227+
&*DEMO_SILO_USER_ID_IN_SILO_URL,
228+
&*DEMO_SILO_USER_LOGOUT_URL,
227229
],
228230
},
229231
// Create the default IP pool

openapi/nexus.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11449,6 +11449,45 @@
1144911449
}
1145011450
}
1145111451
},
11452+
"/v1/users/{user_id}": {
11453+
"get": {
11454+
"tags": [
11455+
"silos"
11456+
],
11457+
"summary": "Fetch user",
11458+
"operationId": "user_view",
11459+
"parameters": [
11460+
{
11461+
"in": "path",
11462+
"name": "user_id",
11463+
"description": "ID of the user",
11464+
"required": true,
11465+
"schema": {
11466+
"type": "string",
11467+
"format": "uuid"
11468+
}
11469+
}
11470+
],
11471+
"responses": {
11472+
"200": {
11473+
"description": "successful operation",
11474+
"content": {
11475+
"application/json": {
11476+
"schema": {
11477+
"$ref": "#/components/schemas/User"
11478+
}
11479+
}
11480+
}
11481+
},
11482+
"4XX": {
11483+
"$ref": "#/components/responses/Error"
11484+
},
11485+
"5XX": {
11486+
"$ref": "#/components/responses/Error"
11487+
}
11488+
}
11489+
}
11490+
},
1145211491
"/v1/users/{user_id}/logout": {
1145311492
"post": {
1145411493
"tags": [

0 commit comments

Comments
 (0)