Skip to content

Commit

Permalink
move the final device_auth thing to nexus/src/app
Browse files Browse the repository at this point in the history
  • Loading branch information
david-crespo committed Jan 22, 2025
1 parent 1491a11 commit e578e4e
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 117 deletions.
89 changes: 87 additions & 2 deletions nexus/src/app/device_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,30 @@
//! In the current implementation, we use long-lived random tokens,
//! but that may change in the future.
use crate::external_api::device_auth::DeviceAccessTokenResponse;
use dropshot::{Body, HttpError};
use http::{header, Response, StatusCode};
use nexus_db_queries::authn::{Actor, Reason};
use nexus_db_queries::authz;
use nexus_db_queries::context::OpContext;
use nexus_db_queries::db::lookup::LookupPath;
use nexus_db_queries::db::model::{DeviceAccessToken, DeviceAuthRequest};

use nexus_types::external_api::params::DeviceAccessTokenRequest;
use nexus_types::external_api::views;
use omicron_common::api::external::{CreateResult, Error};

use chrono::Utc;
use serde::Serialize;
use uuid::Uuid;

#[derive(Debug)]
pub enum DeviceAccessTokenResponse {
Granted(DeviceAccessToken),
Pending,
#[allow(dead_code)]
Denied,
}

impl super::Nexus {
/// Start a device authorization grant flow.
/// Corresponds to steps 1 & 2 in the flow description above.
Expand Down Expand Up @@ -88,7 +100,7 @@ impl super::Nexus {
.fetch()
.await?;

let (.., authz_user) = LookupPath::new(opctx, &self.datastore())
let (.., authz_user) = LookupPath::new(opctx, &self.db_datastore)
.silo_user_id(silo_user_id)
.lookup_for(authz::Action::CreateChild)
.await?;
Expand Down Expand Up @@ -180,4 +192,77 @@ impl super::Nexus {

Ok(Actor::SiloUser { silo_user_id, silo_id })
}

pub(crate) async fn device_access_token(
&self,
opctx: &OpContext,
params: DeviceAccessTokenRequest,
) -> Result<Response<Body>, HttpError> {
// RFC 8628 §3.4
if params.grant_type != "urn:ietf:params:oauth:grant-type:device_code" {
return self.build_oauth_response(
StatusCode::BAD_REQUEST,
&serde_json::json!({
"error": "unsupported_grant_type"
}),
);
}

// RFC 8628 §3.5
use DeviceAccessTokenResponse::*;
match self
.device_access_token_fetch(
&opctx,
params.client_id,
params.device_code,
)
.await
{
Ok(response) => match response {
Granted(token) => self.build_oauth_response(
StatusCode::OK,
&views::DeviceAccessTokenGrant::from(token),
),
Pending => self.build_oauth_response(
StatusCode::BAD_REQUEST,
&serde_json::json!({
"error": "authorization_pending"
}),
),
Denied => self.build_oauth_response(
StatusCode::BAD_REQUEST,
&serde_json::json!({
"error": "access_denied"
}),
),
},
Err(error) => self.build_oauth_response(
StatusCode::BAD_REQUEST,
&serde_json::json!({
"error": "invalid_request",
"error_description": format!("{}", error),
}),
),
}
}

/// OAuth 2.0 error responses use 400 (Bad Request) with specific `error`
/// parameter values to indicate protocol errors (see RFC 6749 §5.2).
/// This is different from Dropshot's error `message` parameter, so we
/// need a custom response builder.
pub(crate) fn build_oauth_response<T>(
&self,
status: StatusCode,
body: &T,
) -> Result<Response<Body>, HttpError>
where
T: ?Sized + Serialize,
{
let body = serde_json::to_string(body)
.map_err(|e| HttpError::for_internal_error(e.to_string()))?;
Ok(Response::builder()
.status(status)
.header(header::CONTENT_TYPE, "application/json")
.body(body.into())?)
}
}
110 changes: 0 additions & 110 deletions nexus/src/external_api/device_auth.rs

This file was deleted.

26 changes: 22 additions & 4 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! Handler functions (entrypoints) for external HTTP APIs
use super::{
console_api, device_auth, params,
console_api, params,
views::{
self, Certificate, FloatingIp, Group, IdentityProvider, Image, IpPool,
IpPoolRange, PhysicalDisk, Project, Rack, Role, Silo, SiloQuotas,
Expand Down Expand Up @@ -6719,6 +6719,13 @@ impl NexusExternalApi for NexusExternalApiImpl {
.await
}

// Entrypoints for the OAuth 2.0 Device Authorization Grant flow.
//
// These are endpoints used by the API client per se (e.g., the CLI),
// *not* the user of that client (e.g., an Oxide rack operator). They
// are for requesting access tokens that will be managed and used by
// the client to make other API requests.

async fn device_auth_request(
rqctx: RequestContext<Self::Context>,
params: TypedBody<params::DeviceAuthRequest>,
Expand All @@ -6732,7 +6739,7 @@ impl NexusExternalApi for NexusExternalApiImpl {
let host = match &authority {
Ok(host) => host.as_str(),
Err(error) => {
return device_auth::build_oauth_response(
return nexus.build_oauth_response(
StatusCode::BAD_REQUEST,
&serde_json::json!({
"error": "invalid_request",
Expand All @@ -6745,7 +6752,7 @@ impl NexusExternalApi for NexusExternalApiImpl {
let model = nexus
.device_auth_request_create(&opctx, params.client_id)
.await?;
device_auth::build_oauth_response(
nexus.build_oauth_response(
StatusCode::OK,
&model.into_response(rqctx.server.using_tls(), host),
)
Expand Down Expand Up @@ -6802,6 +6809,17 @@ impl NexusExternalApi for NexusExternalApiImpl {
rqctx: RequestContext<Self::Context>,
params: TypedBody<params::DeviceAccessTokenRequest>,
) -> Result<Response<Body>, HttpError> {
device_auth::device_access_token(rqctx, params.into_inner()).await
let apictx = rqctx.context();
let handler = async {
let nexus = &apictx.context.nexus;
let opctx = nexus.opctx_external_authn();
let params = params.into_inner();
nexus.device_access_token(&opctx, params).await
};
apictx
.context
.external_latencies
.instrument_dropshot_handler(&rqctx, handler)
.await
}
}
1 change: 0 additions & 1 deletion nexus/src/external_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

pub mod console_api;
pub mod device_auth;
pub(crate) mod http_entrypoints;

pub(crate) use nexus_types::external_api::params;
Expand Down

0 comments on commit e578e4e

Please sign in to comment.