Skip to content

Commit d80a4d6

Browse files
committed
user_view endpoint
1 parent 42d7fd3 commit d80a4d6

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
@@ -158,6 +158,7 @@ policy_update PUT /v1/policy
158158
policy_view GET /v1/policy
159159
user_list GET /v1/users
160160
user_logout POST /v1/users/{user_id}/logout
161+
user_view GET /v1/users/{user_id}
161162
utilization_view GET /v1/utilization
162163

163164
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
@@ -3076,6 +3076,17 @@ pub trait NexusExternalApi {
30763076
query_params: Query<PaginatedById<params::OptionalGroupSelector>>,
30773077
) -> Result<HttpResponseOk<ResultsPage<views::User>>, HttpError>;
30783078

3079+
/// Fetch user
3080+
#[endpoint {
3081+
method = GET,
3082+
path = "/v1/users/{user_id}",
3083+
tags = ["silos"],
3084+
}]
3085+
async fn user_view(
3086+
rqctx: RequestContext<Self::Context>,
3087+
path_params: Path<params::UserPath>,
3088+
) -> Result<HttpResponseOk<views::User>, HttpError>;
3089+
30793090
/// Expire all of user's tokens and sessions
30803091
#[endpoint {
30813092
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
@@ -6860,6 +6860,28 @@ impl NexusExternalApi for NexusExternalApiImpl {
68606860
.await
68616861
}
68626862

6863+
async fn user_view(
6864+
rqctx: RequestContext<Self::Context>,
6865+
path_params: Path<params::UserPath>,
6866+
) -> Result<HttpResponseOk<views::User>, HttpError> {
6867+
let apictx = rqctx.context();
6868+
let handler = async {
6869+
let nexus = &apictx.context.nexus;
6870+
let path = path_params.into_inner();
6871+
let opctx =
6872+
crate::context::op_context_for_external_api(&rqctx).await?;
6873+
let (.., user) = nexus
6874+
.current_silo_user_lookup(&opctx, path.user_id)
6875+
.await?;
6876+
Ok(HttpResponseOk(user.into()))
6877+
};
6878+
apictx
6879+
.context
6880+
.external_latencies
6881+
.instrument_dropshot_handler(&rqctx, handler)
6882+
.await
6883+
}
6884+
68636885
async fn user_logout(
68646886
rqctx: RequestContext<Self::Context>,
68656887
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
@@ -139,6 +139,11 @@ pub static DEMO_SILO_USERS_LIST_URL: LazyLock<String> = LazyLock::new(|| {
139139
pub static DEMO_SILO_USER_ID_GET_URL: LazyLock<String> = LazyLock::new(|| {
140140
format!("/v1/system/users/{{id}}?silo={}", DEFAULT_SILO.identity().name,)
141141
});
142+
pub static DEMO_SILO_USER_ID_IN_SILO_URL: LazyLock<String> =
143+
LazyLock::new(|| "/v1/users/{id}".to_string());
144+
pub static DEMO_SILO_USER_LOGOUT_URL: LazyLock<String> =
145+
LazyLock::new(|| "/v1/users/{id}/logout".to_string());
146+
142147
pub static DEMO_SILO_USER_ID_DELETE_URL: LazyLock<String> =
143148
LazyLock::new(|| {
144149
format!(
@@ -1676,8 +1681,14 @@ pub static VERIFY_ENDPOINTS: LazyLock<Vec<VerifyEndpoint>> =
16761681
allowed_methods: vec![AllowedMethod::Get],
16771682
},
16781683
VerifyEndpoint {
1679-
url: "/v1/users/af47bf12-2eab-4892-8a2f-c064a812c884/logout",
1684+
url: &DEMO_SILO_USER_ID_IN_SILO_URL,
16801685
visibility: Visibility::Protected,
1686+
unprivileged_access: UnprivilegedAccess::ReadOnly,
1687+
allowed_methods: vec![AllowedMethod::Get],
1688+
},
1689+
VerifyEndpoint {
1690+
url: &DEMO_SILO_USER_LOGOUT_URL,
1691+
visibility: Visibility::Public,
16811692
unprivileged_access: UnprivilegedAccess::None,
16821693
allowed_methods: vec![AllowedMethod::Post(serde_json::json!(
16831694
{}

nexus/tests/integration_tests/unauthorized.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ static SETUP_REQUESTS: LazyLock<Vec<SetupReq>> = LazyLock::new(|| {
250250
&*DEMO_SILO_USER_ID_GET_URL,
251251
&*DEMO_SILO_USER_ID_DELETE_URL,
252252
&*DEMO_SILO_USER_ID_SET_PASSWORD_URL,
253+
&*DEMO_SILO_USER_ID_IN_SILO_URL,
254+
&*DEMO_SILO_USER_LOGOUT_URL,
253255
],
254256
},
255257
// Create the default IP pool

openapi/nexus.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11577,6 +11577,45 @@
1157711577
}
1157811578
}
1157911579
},
11580+
"/v1/users/{user_id}": {
11581+
"get": {
11582+
"tags": [
11583+
"silos"
11584+
],
11585+
"summary": "Fetch user",
11586+
"operationId": "user_view",
11587+
"parameters": [
11588+
{
11589+
"in": "path",
11590+
"name": "user_id",
11591+
"description": "ID of the user",
11592+
"required": true,
11593+
"schema": {
11594+
"type": "string",
11595+
"format": "uuid"
11596+
}
11597+
}
11598+
],
11599+
"responses": {
11600+
"200": {
11601+
"description": "successful operation",
11602+
"content": {
11603+
"application/json": {
11604+
"schema": {
11605+
"$ref": "#/components/schemas/User"
11606+
}
11607+
}
11608+
}
11609+
},
11610+
"4XX": {
11611+
"$ref": "#/components/responses/Error"
11612+
},
11613+
"5XX": {
11614+
"$ref": "#/components/responses/Error"
11615+
}
11616+
}
11617+
}
11618+
},
1158011619
"/v1/users/{user_id}/logout": {
1158111620
"post": {
1158211621
"tags": [

0 commit comments

Comments
 (0)