diff --git a/Cargo.lock b/Cargo.lock index a20dd8acce2..1e83bdebbc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2808,6 +2808,24 @@ dependencies = [ "syn", ] +[[package]] +name = "nexus-types" +version = "0.1.0" +dependencies = [ + "anyhow", + "api_identity 0.1.0", + "base64", + "chrono", + "omicron-common 0.1.0", + "openssl", + "openssl-probe", + "openssl-sys", + "schemars", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -3048,7 +3066,6 @@ name = "omicron-nexus" version = "0.1.0" dependencies = [ "anyhow", - "api_identity 0.1.0", "async-bb8-diesel", "async-trait", "authz-macros", @@ -3081,6 +3098,7 @@ dependencies = [ "newtype_derive", "nexus-test-utils", "nexus-test-utils-macros", + "nexus-types", "num-integer", "omicron-common 0.1.0", "omicron-rpaths", diff --git a/Cargo.toml b/Cargo.toml index 90a46f0bdb3..ff81379171e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "nexus/db-macros", "nexus/test-utils", "nexus/test-utils-macros", + "nexus/types", "nexus-client", "package", "rpaths", @@ -45,6 +46,7 @@ default-members = [ "nexus", "nexus/authz-macros", "nexus/db-macros", + "nexus/types", "package", "rpaths", "sled-agent", diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index 580884b75e2..1a3719a6a40 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -60,9 +60,7 @@ usdt = "0.3.1" authz-macros = { path = "authz-macros" } db-macros = { path = "db-macros" } - -[dependencies.api_identity] -path = "../api_identity" +nexus-types = { path = "types" } [dependencies.chrono] version = "0.4" diff --git a/nexus/db-macros/src/lib.rs b/nexus/db-macros/src/lib.rs index 9f89bc8049b..8495e0369ad 100644 --- a/nexus/db-macros/src/lib.rs +++ b/nexus/db-macros/src/lib.rs @@ -339,8 +339,8 @@ fn build_resource_impl( self.identity.id } - fn name(&self) -> &crate::db::model::Name { - &self.identity.name + fn name(&self) -> &::omicron_common::api::external::Name { + &self.identity.name.0 } fn description(&self) -> &str { diff --git a/nexus/src/app/sagas/instance_create.rs b/nexus/src/app/sagas/instance_create.rs index efad2ee5779..d6f6372522c 100644 --- a/nexus/src/app/sagas/instance_create.rs +++ b/nexus/src/app/sagas/instance_create.rs @@ -326,7 +326,7 @@ async fn sic_create_custom_network_interfaces( // // This isn't strictly necessary, as the queries would fail below, but it's // easier to handle here. - if interface_params.iter().any(|p| p.vpc_name != db_vpc.name().0) { + if interface_params.iter().any(|p| &p.vpc_name != db_vpc.name()) { return Err(ActionError::action_failed(Error::invalid_request( "All interfaces must be in the same VPC", ))); @@ -806,7 +806,7 @@ async fn sic_create_instance_record( .await .map_err(ActionError::action_failed)?; - Ok(instance.name().clone()) + Ok(instance.name().clone().into()) } async fn sic_delete_instance_record( diff --git a/nexus/src/app/silo.rs b/nexus/src/app/silo.rs index 73085d91a9e..3632af5d2cd 100644 --- a/nexus/src/app/silo.rs +++ b/nexus/src/app/silo.rs @@ -234,7 +234,7 @@ impl super::Nexus { .ssh_key_name(ssh_key_name) .fetch() .await?; - assert_eq!(ssh_key.name(), ssh_key_name); + assert_eq!(ssh_key.name(), &ssh_key_name.0); Ok(ssh_key) } diff --git a/nexus/src/cidata.rs b/nexus/src/cidata.rs index 3a0e15fdae7..5e0190c78da 100644 --- a/nexus/src/cidata.rs +++ b/nexus/src/cidata.rs @@ -6,7 +6,7 @@ use serde::Serialize; use std::io::{self, Cursor, Write}; use uuid::Uuid; -pub const MAX_USER_DATA_BYTES: usize = 32 * 1024; // 32 KiB +pub use nexus_types::external_api::params::MAX_USER_DATA_BYTES; impl Instance { pub fn generate_cidata( diff --git a/nexus/src/db/datastore/mod.rs b/nexus/src/db/datastore/mod.rs index 8c9a702d934..85e45b42f5d 100644 --- a/nexus/src/db/datastore/mod.rs +++ b/nexus/src/db/datastore/mod.rs @@ -245,6 +245,7 @@ mod test { ByteCount, Error, IdentityMetadataCreateParams, LookupType, Name, }; use omicron_test_utils::dev; + use ref_cast::RefCast; use std::collections::HashSet; use std::net::Ipv6Addr; use std::net::SocketAddrV6; @@ -285,7 +286,9 @@ mod test { let (.., organization_after_project_create) = LookupPath::new(&opctx, &datastore) - .organization_name(organization.name()) + .organization_name(db::model::Name::ref_cast( + organization.name(), + )) .fetch() .await .unwrap(); diff --git a/nexus/src/db/mod.rs b/nexus/src/db/mod.rs index c70fcdf0f07..1aa79c9427c 100644 --- a/nexus/src/db/mod.rs +++ b/nexus/src/db/mod.rs @@ -32,7 +32,6 @@ mod update_and_check; #[cfg(test)] mod test_utils; -pub mod identity; pub mod model; pub mod schema; @@ -42,3 +41,5 @@ pub use pool::Pool; pub use saga_recovery::{recover, RecoveryTask}; pub use saga_types::SecId; pub use sec_store::CockroachDbSecStore; + +pub use nexus_types::identity; diff --git a/nexus/src/db/model/device_auth.rs b/nexus/src/db/model/device_auth.rs index b0a0703c5ea..d1137e3672c 100644 --- a/nexus/src/db/model/device_auth.rs +++ b/nexus/src/db/model/device_auth.rs @@ -9,6 +9,7 @@ use crate::db::schema::{device_access_token, device_auth_request}; use chrono::{DateTime, Duration, Utc}; +use nexus_types::external_api::views; use rand::{distributions::Slice, rngs::StdRng, Rng, RngCore, SeedableRng}; use uuid::Uuid; @@ -29,6 +30,26 @@ pub struct DeviceAuthRequest { pub time_expires: DateTime, } +impl DeviceAuthRequest { + // We need the host to construct absolute verification URIs. + pub fn into_response(self, host: &str) -> views::DeviceAuthResponse { + views::DeviceAuthResponse { + // TODO-security: use HTTPS + verification_uri: format!("http://{}/device/verify", host), + verification_uri_complete: format!( + "http://{}/device/verify?user_code={}", + host, &self.user_code + ), + user_code: self.user_code, + device_code: self.device_code, + expires_in: self + .time_expires + .signed_duration_since(self.time_created) + .num_seconds() as u16, + } + } +} + /// Neither the device code nor the access token is meant to be /// human-readable, so we use 20 random bytes (160 bits), hex-encoded. const TOKEN_LENGTH: usize = 20; @@ -135,6 +156,15 @@ impl DeviceAccessToken { } } +impl From for views::DeviceAccessTokenGrant { + fn from(access_token: DeviceAccessToken) -> Self { + Self { + access_token: format!("oxide-token-{}", access_token.token), + token_type: views::DeviceAccessTokenType::Bearer, + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/nexus/src/db/model/identity_provider.rs b/nexus/src/db/model/identity_provider.rs index 6626136e9fb..38427063f8a 100644 --- a/nexus/src/db/model/identity_provider.rs +++ b/nexus/src/db/model/identity_provider.rs @@ -2,10 +2,12 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::db::identity::Resource; use crate::db::model::impl_enum_type; use crate::db::schema::{identity_provider, saml_identity_provider}; use db_macros::Resource; +use nexus_types::external_api::views; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -22,6 +24,14 @@ impl_enum_type!( Saml => b"saml" ); +impl From for views::IdentityProviderType { + fn from(idp_type: IdentityProviderType) -> Self { + match idp_type { + IdentityProviderType::Saml => views::IdentityProviderType::Saml, + } + } +} + #[derive(Queryable, Insertable, Clone, Debug, Selectable, Resource)] #[diesel(table_name = identity_provider)] pub struct IdentityProvider { @@ -33,6 +43,15 @@ pub struct IdentityProvider { pub provider_type: IdentityProviderType, } +impl From for views::IdentityProvider { + fn from(idp: IdentityProvider) -> Self { + Self { + identity: idp.identity(), + provider_type: idp.provider_type.into(), + } + } +} + #[derive(Queryable, Insertable, Clone, Debug, Selectable, Resource)] #[diesel(table_name = saml_identity_provider)] pub struct SamlIdentityProvider { @@ -51,3 +70,17 @@ pub struct SamlIdentityProvider { pub public_cert: Option, pub private_key: Option, } + +impl From for views::SamlIdentityProvider { + fn from(saml_idp: SamlIdentityProvider) -> Self { + Self { + identity: saml_idp.identity(), + idp_entity_id: saml_idp.idp_entity_id, + sp_client_id: saml_idp.sp_client_id, + acs_url: saml_idp.acs_url, + slo_url: saml_idp.slo_url, + technical_contact_email: saml_idp.technical_contact_email, + public_cert: saml_idp.public_cert, + } + } +} diff --git a/nexus/src/db/model/ip_pool.rs b/nexus/src/db/model/ip_pool.rs index e230750bac9..0aadc87ffb0 100644 --- a/nexus/src/db/model/ip_pool.rs +++ b/nexus/src/db/model/ip_pool.rs @@ -5,6 +5,7 @@ //! Model types for IP Pools and the CIDR blocks therein. use crate::db::collection_insert::DatastoreCollection; +use crate::db::identity::Resource; use crate::db::model::Name; use crate::db::schema::ip_pool; use crate::db::schema::ip_pool_range; @@ -15,6 +16,7 @@ use chrono::Utc; use db_macros::Resource; use diesel::Selectable; use ipnetwork::IpNetwork; +use nexus_types::external_api::views; use omicron_common::api::external; use std::net::IpAddr; use uuid::Uuid; @@ -50,6 +52,12 @@ impl IpPool { } } +impl From for views::IpPool { + fn from(pool: IpPool) -> Self { + Self { identity: pool.identity(), project_id: pool.project_id } + } +} + /// A set of updates to an IP Pool #[derive(AsChangeset)] #[diesel(table_name = ip_pool)] @@ -120,6 +128,16 @@ impl IpPoolRange { } } +impl From for views::IpPoolRange { + fn from(range: IpPoolRange) -> Self { + Self { + id: range.id, + time_created: range.time_created, + range: IpRange::from(&range), + } + } +} + impl From<&IpPoolRange> for IpRange { fn from(range: &IpPoolRange) -> Self { let maybe_range = diff --git a/nexus/src/db/model/organization.rs b/nexus/src/db/model/organization.rs index 0dadaa09dfd..10171b147c1 100644 --- a/nexus/src/db/model/organization.rs +++ b/nexus/src/db/model/organization.rs @@ -4,10 +4,12 @@ use super::{Generation, Name, Project}; use crate::db::collection_insert::DatastoreCollection; +use crate::db::identity::Resource; use crate::db::schema::{organization, project}; use crate::external_api::params; use chrono::{DateTime, Utc}; use db_macros::Resource; +use nexus_types::external_api::views; use uuid::Uuid; /// Describes an organization within the database. @@ -35,6 +37,12 @@ impl Organization { } } +impl From for views::Organization { + fn from(org: Organization) -> Self { + Self { identity: org.identity() } + } +} + impl DatastoreCollection for Organization { type CollectionId = Uuid; type GenerationNumberColumn = organization::dsl::rcgen; diff --git a/nexus/src/db/model/project.rs b/nexus/src/db/model/project.rs index 6cd7de87858..5e47b424086 100644 --- a/nexus/src/db/model/project.rs +++ b/nexus/src/db/model/project.rs @@ -3,10 +3,11 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::Name; -use crate::db::schema::project; +use crate::db::{identity::Resource, schema::project}; use crate::external_api::params; use chrono::{DateTime, Utc}; use db_macros::Resource; +use nexus_types::external_api::views; use uuid::Uuid; /// Describes a project within the database. @@ -29,6 +30,15 @@ impl Project { } } +impl From for views::Project { + fn from(project: Project) -> Self { + Self { + identity: project.identity(), + organization_id: project.organization_id, + } + } +} + /// Describes a set of updates for the [`Project`] model. #[derive(AsChangeset)] #[diesel(table_name = project)] diff --git a/nexus/src/db/model/rack.rs b/nexus/src/db/model/rack.rs index 554ea8139f0..cc6916ee24b 100644 --- a/nexus/src/db/model/rack.rs +++ b/nexus/src/db/model/rack.rs @@ -4,6 +4,7 @@ use crate::db::schema::rack; use db_macros::Asset; +use nexus_types::external_api::views; use uuid::Uuid; /// Information about a local rack. @@ -26,3 +27,9 @@ impl Rack { } } } + +impl From for views::Rack { + fn from(rack: Rack) -> Self { + Self { identity: views::AssetIdentityMetadata::from(&rack) } + } +} diff --git a/nexus/src/db/model/role_assignment.rs b/nexus/src/db/model/role_assignment.rs index 6de2a4e6fa4..a8ecb04fc79 100644 --- a/nexus/src/db/model/role_assignment.rs +++ b/nexus/src/db/model/role_assignment.rs @@ -2,9 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use super::impl_enum_type; +use super::{impl_enum_type, DatabaseString}; use crate::db::schema::role_assignment; use crate::external_api::shared; +use anyhow::anyhow; +use omicron_common::api::external::Error; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -38,6 +40,19 @@ impl From for IdentityType { } } +impl TryFrom for shared::IdentityType { + type Error = anyhow::Error; + + fn try_from(other: IdentityType) -> Result { + match other { + IdentityType::UserBuiltin => { + Err(anyhow!("unsupported db identity type: {:?}", other)) + } + IdentityType::SiloUser => Ok(shared::IdentityType::SiloUser), + } + } +} + /// Describes an assignment of a built-in role for a user #[derive(Clone, Queryable, Insertable, Debug, Selectable)] #[diesel(table_name = role_assignment)] @@ -67,3 +82,132 @@ impl RoleAssignment { } } } + +impl TryFrom + for shared::RoleAssignment +where + AllowedRoles: DatabaseString, +{ + type Error = Error; + + fn try_from(role_asgn: RoleAssignment) -> Result { + Ok(Self { + identity_type: shared::IdentityType::try_from( + role_asgn.identity_type, + ) + .map_err(|error| { + Error::internal_error(&format!( + "parsing database role assignment: {:#}", + error + )) + })?, + identity_id: role_asgn.identity_id, + role_name: AllowedRoles::from_database_string(&role_asgn.role_name) + .map_err(|error| { + Error::internal_error(&format!( + "parsing database role assignment: \ + unrecognized role name {:?}: {:#}", + &role_asgn.role_name, error, + )) + })?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db; + use omicron_common::api::external::ResourceType; + + #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] + #[serde(rename_all = "kebab-case")] + pub enum DummyRoles { + Bogus, + } + + impl db::model::DatabaseString for DummyRoles { + type Error = anyhow::Error; + + fn to_database_string(&self) -> &str { + unimplemented!() + } + + fn from_database_string(s: &str) -> Result { + if s == "bogus" { + Ok(DummyRoles::Bogus) + } else { + Err(anyhow!("unsupported DummyRoles: {:?}", s)) + } + } + } + + #[test] + fn test_role_assignment_from_database() { + let identity_id = + "75ec4a39-67cf-4549-9e74-44b92947c37c".parse().unwrap(); + let resource_id = + "9e3e3be8-4051-4ddb-92fa-32cc5294f066".parse().unwrap(); + + let ok_input = db::model::RoleAssignment { + identity_type: db::model::IdentityType::SiloUser, + identity_id, + resource_type: ResourceType::Organization.to_string(), + resource_id, + role_name: String::from("bogus"), + }; + + let bad_input_role = db::model::RoleAssignment { + role_name: String::from("bogosity"), + ..ok_input.clone() + }; + + let bad_input_idtype = db::model::RoleAssignment { + identity_type: db::model::IdentityType::UserBuiltin, + ..ok_input.clone() + }; + + let error = + >::try_from(bad_input_role) + .expect_err("unexpectedly succeeding parsing database role"); + println!("error: {:#}", error); + if let Error::InternalError { internal_message } = error { + assert_eq!( + internal_message, + "parsing database role assignment: unrecognized role name \ + \"bogosity\": unsupported DummyRoles: \"bogosity\"" + ); + } else { + panic!( + "expected internal error for database parse failure, \ + found {:?}", + error + ); + } + + let error = + >::try_from(bad_input_idtype) + .expect_err("unexpectedly succeeding parsing database role"); + println!("error: {:#}", error); + if let Error::InternalError { internal_message } = error { + assert_eq!( + internal_message, + "parsing database role assignment: \ + unsupported db identity type: UserBuiltin" + ); + } else { + panic!( + "expected internal error for database parse failure, \ + found {:?}", + error + ); + } + + let success = >::try_from(ok_input) + .expect("parsing valid role assignment from database"); + println!("success: {:?}", success); + assert_eq!(success.identity_type, shared::IdentityType::SiloUser); + assert_eq!(success.identity_id, identity_id); + assert_eq!(success.role_name, DummyRoles::Bogus); + } +} diff --git a/nexus/src/db/model/role_builtin.rs b/nexus/src/db/model/role_builtin.rs index 1fea46491ea..a088a34501b 100644 --- a/nexus/src/db/model/role_builtin.rs +++ b/nexus/src/db/model/role_builtin.rs @@ -3,6 +3,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::db::schema::role_builtin; +use nexus_types::external_api::views; +use omicron_common::api::external::RoleName; /// Describes a built-in role, as stored in the database #[derive(Queryable, Insertable, Debug, Selectable)] @@ -31,3 +33,12 @@ impl RoleBuiltin { (self.resource_type.clone(), self.role_name.clone()) } } + +impl From for views::Role { + fn from(role: RoleBuiltin) -> Self { + Self { + name: RoleName::new(&role.resource_type, &role.role_name), + description: role.description, + } + } +} diff --git a/nexus/src/db/model/silo.rs b/nexus/src/db/model/silo.rs index f10b5a5c433..e2f2dcadbf7 100644 --- a/nexus/src/db/model/silo.rs +++ b/nexus/src/db/model/silo.rs @@ -4,10 +4,12 @@ use super::{Generation, Organization}; use crate::db::collection_insert::DatastoreCollection; +use crate::db::identity::Resource; use crate::db::model::impl_enum_type; use crate::db::schema::{organization, silo}; use crate::external_api::{params, shared}; use db_macros::Resource; +use nexus_types::external_api::views; use uuid::Uuid; impl_enum_type!( @@ -33,6 +35,15 @@ impl From for UserProvisionType { } } +impl From for shared::UserProvisionType { + fn from(model: UserProvisionType) -> Self { + match model { + UserProvisionType::Fixed => Self::Fixed, + UserProvisionType::Jit => Self::Jit, + } + } +} + /// Describes a silo within the database. #[derive(Queryable, Insertable, Debug, Resource, Selectable)] #[diesel(table_name = silo)] @@ -64,6 +75,16 @@ impl Silo { } } +impl From for views::Silo { + fn from(silo: Silo) -> Self { + Self { + identity: silo.identity(), + discoverable: silo.discoverable, + user_provision_type: silo.user_provision_type.into(), + } + } +} + impl DatastoreCollection for Silo { type CollectionId = Uuid; type GenerationNumberColumn = silo::dsl::rcgen; diff --git a/nexus/src/db/model/silo_user.rs b/nexus/src/db/model/silo_user.rs index be430e69c74..2365ce45589 100644 --- a/nexus/src/db/model/silo_user.rs +++ b/nexus/src/db/model/silo_user.rs @@ -2,8 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::db::identity::Asset; use crate::db::schema::silo_user; use db_macros::Asset; +use nexus_types::external_api::views; use uuid::Uuid; /// Describes a silo user within the database. @@ -24,3 +26,13 @@ impl SiloUser { Self { identity: SiloUserIdentity::new(user_id), silo_id, external_id } } } + +impl From for views::User { + fn from(user: SiloUser) -> Self { + Self { + id: user.id(), + // TODO the use of external_id as display_name is temporary + display_name: user.external_id, + } + } +} diff --git a/nexus/src/db/model/sled.rs b/nexus/src/db/model/sled.rs index ebe492c7459..8bcb122359b 100644 --- a/nexus/src/db/model/sled.rs +++ b/nexus/src/db/model/sled.rs @@ -8,6 +8,7 @@ use crate::db::ipv6; use crate::db::schema::{service, sled, zpool}; use chrono::{DateTime, Utc}; use db_macros::Asset; +use nexus_types::external_api::views; use std::net::Ipv6Addr; use std::net::SocketAddrV6; use uuid::Uuid; @@ -62,6 +63,15 @@ impl Sled { } } +impl From for views::Sled { + fn from(sled: Sled) -> Self { + Self { + identity: views::AssetIdentityMetadata::from(&sled), + service_address: sled.address(), + } + } +} + impl DatastoreCollection for Sled { type CollectionId = Uuid; type GenerationNumberColumn = sled::dsl::rcgen; diff --git a/nexus/src/db/model/ssh_key.rs b/nexus/src/db/model/ssh_key.rs index 900d67fc511..a0726ebf093 100644 --- a/nexus/src/db/model/ssh_key.rs +++ b/nexus/src/db/model/ssh_key.rs @@ -2,9 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::db::identity::Resource; use crate::db::schema::ssh_key; use crate::external_api::params; use db_macros::Resource; +use nexus_types::external_api::views; use uuid::Uuid; /// Describes a user's public SSH key within the database. @@ -35,3 +37,13 @@ impl SshKey { } } } + +impl From for views::SshKey { + fn from(ssh_key: SshKey) -> Self { + Self { + identity: ssh_key.identity(), + silo_user_id: ssh_key.silo_user_id, + public_key: ssh_key.public_key, + } + } +} diff --git a/nexus/src/db/model/user_builtin.rs b/nexus/src/db/model/user_builtin.rs index 22ad440e86f..7f046910146 100644 --- a/nexus/src/db/model/user_builtin.rs +++ b/nexus/src/db/model/user_builtin.rs @@ -2,9 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::db::identity::Resource; use crate::db::schema::user_builtin; use crate::external_api::params; use db_macros::Resource; +use nexus_types::external_api::views; use uuid::Uuid; /// Describes a built-in user, as stored in the database @@ -21,3 +23,9 @@ impl UserBuiltin { Self { identity: UserBuiltinIdentity::new(id, params.identity) } } } + +impl From for views::UserBuiltin { + fn from(user: UserBuiltin) -> Self { + Self { identity: user.identity() } + } +} diff --git a/nexus/src/db/model/vpc.rs b/nexus/src/db/model/vpc.rs index 6c6ed7c826b..51058b5d0c7 100644 --- a/nexus/src/db/model/vpc.rs +++ b/nexus/src/db/model/vpc.rs @@ -4,6 +4,7 @@ use super::{Generation, Ipv6Net, Name, VpcFirewallRule}; use crate::db::collection_insert::DatastoreCollection; +use crate::db::identity::Resource; use crate::db::model::Vni; use crate::db::schema::{vpc, vpc_firewall_rule}; use crate::defaults; @@ -11,6 +12,7 @@ use crate::external_api::params; use chrono::{DateTime, Utc}; use db_macros::Resource; use ipnetwork::IpNetwork; +use nexus_types::external_api::views; use omicron_common::api::external; use uuid::Uuid; @@ -31,6 +33,18 @@ pub struct Vpc { pub firewall_gen: Generation, } +impl From for views::Vpc { + fn from(vpc: Vpc) -> Self { + Self { + identity: vpc.identity(), + project_id: vpc.project_id, + system_router_id: vpc.system_router_id, + ipv6_prefix: *vpc.ipv6_prefix, + dns_name: vpc.dns_name.0, + } + } +} + /// An `IncompleteVpc` is a candidate VPC, where some of the values may be /// modified and returned as part of the query inserting it into the database. /// In particular, the requested VNI may not actually be available, in which diff --git a/nexus/src/db/model/vpc_router.rs b/nexus/src/db/model/vpc_router.rs index fd55da0dbea..e8047332d1f 100644 --- a/nexus/src/db/model/vpc_router.rs +++ b/nexus/src/db/model/vpc_router.rs @@ -4,10 +4,12 @@ use super::{impl_enum_type, Generation, Name, RouterRoute}; use crate::db::collection_insert::DatastoreCollection; +use crate::db::identity::Resource; use crate::db::schema::{router_route, vpc_router}; use crate::external_api::params; use chrono::{DateTime, Utc}; use db_macros::Resource; +use nexus_types::external_api::views; use uuid::Uuid; impl_enum_type!( @@ -24,6 +26,15 @@ impl_enum_type!( Custom => b"custom" ); +impl From for views::VpcRouterKind { + fn from(kind: VpcRouterKind) -> Self { + match kind { + VpcRouterKind::Custom => Self::Custom, + VpcRouterKind::System => Self::System, + } + } +} + #[derive(Queryable, Insertable, Clone, Debug, Selectable, Resource)] #[diesel(table_name = vpc_router)] pub struct VpcRouter { @@ -47,6 +58,16 @@ impl VpcRouter { } } +impl From for views::VpcRouter { + fn from(router: VpcRouter) -> Self { + Self { + identity: router.identity(), + vpc_id: router.vpc_id, + kind: router.kind.into(), + } + } +} + impl DatastoreCollection for VpcRouter { type CollectionId = Uuid; type GenerationNumberColumn = vpc_router::dsl::rcgen; diff --git a/nexus/src/db/model/vpc_subnet.rs b/nexus/src/db/model/vpc_subnet.rs index bbb6f28eeaa..d8370ca6c78 100644 --- a/nexus/src/db/model/vpc_subnet.rs +++ b/nexus/src/db/model/vpc_subnet.rs @@ -3,10 +3,11 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::{Ipv4Net, Ipv6Net, Name}; -use crate::db::schema::vpc_subnet; +use crate::db::{identity::Resource, schema::vpc_subnet}; use crate::external_api::params; use chrono::{DateTime, Utc}; use db_macros::Resource; +use nexus_types::external_api::views; use omicron_common::api::external; use std::net::IpAddr; use uuid::Uuid; @@ -75,6 +76,17 @@ impl VpcSubnet { } } +impl From for views::VpcSubnet { + fn from(subnet: VpcSubnet) -> Self { + Self { + identity: subnet.identity(), + vpc_id: subnet.vpc_id, + ipv4_block: subnet.ipv4_block.0, + ipv6_block: subnet.ipv6_block.0, + } + } +} + #[derive(AsChangeset)] #[diesel(table_name = vpc_subnet)] pub struct VpcSubnetUpdate { diff --git a/nexus/src/db/queries/network_interface.rs b/nexus/src/db/queries/network_interface.rs index a0e0177a302..a9b0a8dbaf4 100644 --- a/nexus/src/db/queries/network_interface.rs +++ b/nexus/src/db/queries/network_interface.rs @@ -2307,7 +2307,7 @@ mod tests { inserted: &NetworkInterface, ) { assert_eq!(inserted.id(), incomplete.identity.id); - assert_eq!(inserted.name(), &incomplete.identity.name); + assert_eq!(inserted.name(), &incomplete.identity.name.0); assert_eq!(inserted.description(), incomplete.identity.description); assert_eq!(inserted.instance_id, incomplete.instance_id); assert_eq!(inserted.vpc_id, incomplete.vpc_id); @@ -2395,7 +2395,7 @@ mod tests { Uuid::new_v4(), context.net1.vpc_id, IdentityMetadataCreateParams { - name: context.net1.subnets[0].name().clone().0, + name: context.net1.subnets[0].name().clone(), description: context.net1.subnets[0] .description() .to_string(), @@ -2444,7 +2444,7 @@ mod tests { Uuid::new_v4(), context.net1.vpc_id, IdentityMetadataCreateParams { - name: context.net1.subnets[0].name().clone().0, + name: context.net1.subnets[0].name().clone(), description: context.net1.subnets[0].description().to_string(), }, *context.net1.subnets[0].ipv4_block, diff --git a/nexus/src/db/queries/vpc_subnet.rs b/nexus/src/db/queries/vpc_subnet.rs index b49d52d79d3..842f4c62482 100644 --- a/nexus/src/db/queries/vpc_subnet.rs +++ b/nexus/src/db/queries/vpc_subnet.rs @@ -13,6 +13,7 @@ use diesel::prelude::*; use diesel::query_builder::*; use diesel::sql_types; use omicron_common::api::external; +use ref_cast::RefCast; use uuid::Uuid; /// Errors related to allocating VPC Subnets. @@ -279,7 +280,7 @@ impl QueryFragment for FilterConflictingVpcSubnetRangesQuery { out.push_bind_param::(&self.subnet.identity.id)?; out.push_sql(", "); out.push_bind_param::( - &self.subnet.name(), + db::model::Name::ref_cast(self.subnet.name()), )?; out.push_sql(", "); out.push_bind_param::( diff --git a/nexus/src/external_api/device_auth.rs b/nexus/src/external_api/device_auth.rs index a34fdf2404b..16509d3b117 100644 --- a/nexus/src/external_api/device_auth.rs +++ b/nexus/src/external_api/device_auth.rs @@ -10,7 +10,7 @@ //! the client to make other API requests. use super::console_api::console_index_or_login_redirect; -use super::views::{DeviceAccessTokenGrant, DeviceAuthResponse}; +use super::views::DeviceAccessTokenGrant; use crate::context::OpContext; use crate::db::model::DeviceAccessToken; use crate::ServerContext; @@ -99,10 +99,7 @@ pub async fn device_auth_request( let model = nexus.device_auth_request_create(&opctx, params.client_id).await?; - build_oauth_response( - StatusCode::OK, - &DeviceAuthResponse::from_model(model, host), - ) + build_oauth_response(StatusCode::OK, &model.into_response(host)) }; // TODO: instrumentation doesn't work because we use `Response` //apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await diff --git a/nexus/src/external_api/mod.rs b/nexus/src/external_api/mod.rs index 2d228e62558..d9099e4235f 100644 --- a/nexus/src/external_api/mod.rs +++ b/nexus/src/external_api/mod.rs @@ -5,6 +5,7 @@ pub mod console_api; pub mod device_auth; pub mod http_entrypoints; -pub mod params; -pub mod shared; -pub mod views; + +pub use nexus_types::external_api::params; +pub use nexus_types::external_api::shared; +pub use nexus_types::external_api::views; diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml new file mode 100644 index 00000000000..1be5c6a1ed0 --- /dev/null +++ b/nexus/types/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nexus-types" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[dependencies] +anyhow = "1.0" +chrono = { version = "0.4", features = ["serde"] } +base64 = "0.13.0" +# must match samael's crate! +openssl = "0.10" +openssl-sys = "0.9" +openssl-probe = "0.1.2" +schemars = { version = "0.8.10", features = ["chrono", "uuid1"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "1.1.0", features = ["serde", "v4"] } + +api_identity = { path = "../../api_identity" } +omicron-common = { path = "../../common" } diff --git a/nexus/types/src/external_api/mod.rs b/nexus/types/src/external_api/mod.rs new file mode 100644 index 00000000000..e95beb865b1 --- /dev/null +++ b/nexus/types/src/external_api/mod.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +pub mod params; +pub mod shared; +pub mod views; diff --git a/nexus/src/external_api/params.rs b/nexus/types/src/external_api/params.rs similarity index 99% rename from nexus/src/external_api/params.rs rename to nexus/types/src/external_api/params.rs index e1a896be408..8b42b0b34aa 100644 --- a/nexus/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -453,6 +453,10 @@ pub struct InstanceCreate { pub disks: Vec, } +// If you change this, also update the error message in +// `UserData::deserialize()` below. +pub const MAX_USER_DATA_BYTES: usize = 32 * 1024; // 32 KiB + struct UserData; impl UserData { pub fn serialize( @@ -472,7 +476,7 @@ impl UserData { match base64::decode(<&str>::deserialize(deserializer)?) { Ok(buf) => { // if you change this, also update the stress test in crate::cidata - if buf.len() > crate::cidata::MAX_USER_DATA_BYTES { + if buf.len() > MAX_USER_DATA_BYTES { Err(::invalid_length( buf.len(), &"less than 32 KiB", diff --git a/nexus/src/external_api/shared.rs b/nexus/types/src/external_api/shared.rs similarity index 72% rename from nexus/src/external_api/shared.rs rename to nexus/types/src/external_api/shared.rs index bd8bae80311..1530072320a 100644 --- a/nexus/src/external_api/shared.rs +++ b/nexus/types/src/external_api/shared.rs @@ -4,8 +4,6 @@ //! Types that are used as both views and params -use crate::db; -use anyhow::anyhow; use omicron_common::api::external::Error; use schemars::JsonSchema; use serde::de::Error as _; @@ -80,37 +78,6 @@ pub struct RoleAssignment { pub role_name: AllowedRoles, } -impl TryFrom - for RoleAssignment -where - AllowedRoles: db::model::DatabaseString, -{ - type Error = Error; - - fn try_from( - role_asgn: db::model::RoleAssignment, - ) -> Result { - Ok(Self { - identity_type: IdentityType::try_from(role_asgn.identity_type) - .map_err(|error| { - Error::internal_error(&format!( - "parsing database role assignment: {:#}", - error - )) - })?, - identity_id: role_asgn.identity_id, - role_name: AllowedRoles::from_database_string(&role_asgn.role_name) - .map_err(|error| { - Error::internal_error(&format!( - "parsing database role assignment: \ - unrecognized role name {:?}: {:#}", - &role_asgn.role_name, error, - )) - })?, - }) - } -} - /// Describes what kind of identity is described by an id // This is a subset of the identity types that might be found in the database // because we do not expose some (e.g., built-in users) externally. @@ -122,19 +89,6 @@ pub enum IdentityType { SiloUser, } -impl TryFrom for IdentityType { - type Error = anyhow::Error; - - fn try_from(other: db::model::IdentityType) -> Result { - match other { - db::model::IdentityType::UserBuiltin => { - Err(anyhow!("unsupported db identity type: {:?}", other)) - } - db::model::IdentityType::SiloUser => Ok(IdentityType::SiloUser), - } - } -} - /// How users will be provisioned in a silo during authentication. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -148,15 +102,6 @@ pub enum UserProvisionType { Jit, } -impl From for UserProvisionType { - fn from(model: db::model::UserProvisionType) -> UserProvisionType { - match model { - db::model::UserProvisionType::Fixed => UserProvisionType::Fixed, - db::model::UserProvisionType::Jit => UserProvisionType::Jit, - } - } -} - /// An IP Range is a contiguous range of IP addresses, usually within an IP /// Pool. /// @@ -330,14 +275,12 @@ mod test { use super::IdentityType; use super::Policy; use super::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE; - use crate::db; use crate::external_api::shared; use crate::external_api::shared::IpRange; use crate::external_api::shared::Ipv4Range; use crate::external_api::shared::Ipv6Range; use anyhow::anyhow; use omicron_common::api::external::Error; - use omicron_common::api::external::ResourceType; use serde::Deserialize; use std::net::Ipv4Addr; use std::net::Ipv6Addr; @@ -347,21 +290,6 @@ mod test { pub enum DummyRoles { Bogus, } - impl db::model::DatabaseString for DummyRoles { - type Error = anyhow::Error; - - fn to_database_string(&self) -> &str { - unimplemented!() - } - - fn from_database_string(s: &str) -> Result { - if s == "bogus" { - Ok(DummyRoles::Bogus) - } else { - Err(anyhow!("unsupported DummyRoles: {:?}", s)) - } - } - } #[test] fn test_policy_parsing() { @@ -395,75 +323,6 @@ mod test { ); } - #[test] - fn test_role_assignment_from_database() { - let identity_id = - "75ec4a39-67cf-4549-9e74-44b92947c37c".parse().unwrap(); - let resource_id = - "9e3e3be8-4051-4ddb-92fa-32cc5294f066".parse().unwrap(); - - let ok_input = db::model::RoleAssignment { - identity_type: db::model::IdentityType::SiloUser, - identity_id, - resource_type: ResourceType::Organization.to_string(), - resource_id, - role_name: String::from("bogus"), - }; - - let bad_input_role = db::model::RoleAssignment { - role_name: String::from("bogosity"), - ..ok_input.clone() - }; - - let bad_input_idtype = db::model::RoleAssignment { - identity_type: db::model::IdentityType::UserBuiltin, - ..ok_input.clone() - }; - - let error = - >::try_from(bad_input_role) - .expect_err("unexpectedly succeeding parsing database role"); - println!("error: {:#}", error); - if let Error::InternalError { internal_message } = error { - assert_eq!( - internal_message, - "parsing database role assignment: unrecognized role name \ - \"bogosity\": unsupported DummyRoles: \"bogosity\"" - ); - } else { - panic!( - "expected internal error for database parse failure, \ - found {:?}", - error - ); - } - - let error = - >::try_from(bad_input_idtype) - .expect_err("unexpectedly succeeding parsing database role"); - println!("error: {:#}", error); - if let Error::InternalError { internal_message } = error { - assert_eq!( - internal_message, - "parsing database role assignment: \ - unsupported db identity type: UserBuiltin" - ); - } else { - panic!( - "expected internal error for database parse failure, \ - found {:?}", - error - ); - } - - let success = >::try_from(ok_input) - .expect("parsing valid role assignment from database"); - println!("success: {:?}", success); - assert_eq!(success.identity_type, IdentityType::SiloUser); - assert_eq!(success.identity_id, identity_id); - assert_eq!(success.role_name, DummyRoles::Bogus); - } - #[test] fn test_ip_range_checks_non_decreasing() { let lo = Ipv4Addr::new(10, 0, 0, 1); diff --git a/nexus/src/external_api/views.rs b/nexus/types/src/external_api/views.rs similarity index 65% rename from nexus/src/external_api/views.rs rename to nexus/types/src/external_api/views.rs index 25c6a073cfa..081cd459d55 100644 --- a/nexus/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -4,9 +4,8 @@ //! Views are response bodies, most of which are public lenses onto DB models. -use crate::db::identity::{Asset, Resource}; -use crate::db::model; use crate::external_api::shared::{self, IpKind, IpRange}; +use crate::identity::Asset; use api_identity::ObjectIdentity; use chrono::DateTime; use chrono::Utc; @@ -63,16 +62,6 @@ pub struct Silo { pub user_provision_type: shared::UserProvisionType, } -impl Into for model::Silo { - fn into(self) -> Silo { - Silo { - identity: self.identity(), - discoverable: self.discoverable, - user_provision_type: self.user_provision_type.into(), - } - } -} - // IDENTITY PROVIDER #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] @@ -82,14 +71,6 @@ pub enum IdentityProviderType { Saml, } -impl Into for model::IdentityProviderType { - fn into(self) -> IdentityProviderType { - match self { - model::IdentityProviderType::Saml => IdentityProviderType::Saml, - } - } -} - /// Client view of an [`IdentityProvider`] #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct IdentityProvider { @@ -100,15 +81,6 @@ pub struct IdentityProvider { pub provider_type: IdentityProviderType, } -impl Into for model::IdentityProvider { - fn into(self) -> IdentityProvider { - IdentityProvider { - identity: self.identity(), - provider_type: self.provider_type.into(), - } - } -} - #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct SamlIdentityProvider { #[serde(flatten)] @@ -133,20 +105,6 @@ pub struct SamlIdentityProvider { pub public_cert: Option, } -impl From for SamlIdentityProvider { - fn from(saml_idp: model::SamlIdentityProvider) -> Self { - Self { - identity: saml_idp.identity(), - idp_entity_id: saml_idp.idp_entity_id, - sp_client_id: saml_idp.sp_client_id, - acs_url: saml_idp.acs_url, - slo_url: saml_idp.slo_url, - technical_contact_email: saml_idp.technical_contact_email, - public_cert: saml_idp.public_cert, - } - } -} - // ORGANIZATIONS /// Client view of an [`Organization`] @@ -157,12 +115,6 @@ pub struct Organization { // Important: Silo ID does not get presented to user } -impl From for Organization { - fn from(org: model::Organization) -> Self { - Self { identity: org.identity() } - } -} - // PROJECTS /// Client view of a [`Project`] @@ -175,15 +127,6 @@ pub struct Project { pub organization_id: Uuid, } -impl From for Project { - fn from(project: model::Project) -> Self { - Self { - identity: project.identity(), - organization_id: project.organization_id, - } - } -} - // IMAGES /// Client view of global Images @@ -271,18 +214,6 @@ pub struct Vpc { pub dns_name: Name, } -impl From for Vpc { - fn from(vpc: model::Vpc) -> Self { - Self { - identity: vpc.identity(), - project_id: vpc.project_id, - system_router_id: vpc.system_router_id, - ipv6_prefix: *vpc.ipv6_prefix, - dns_name: vpc.dns_name.0, - } - } -} - /// A VPC subnet represents a logical grouping for instances that allows network traffic between /// them, within a IPv4 subnetwork or optionall an IPv6 subnetwork. #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] @@ -301,17 +232,6 @@ pub struct VpcSubnet { pub ipv6_block: Ipv6Net, } -impl From for VpcSubnet { - fn from(subnet: model::VpcSubnet) -> Self { - Self { - identity: subnet.identity(), - vpc_id: subnet.vpc_id, - ipv4_block: subnet.ipv4_block.0, - ipv6_block: subnet.ipv6_block.0, - } - } -} - #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum VpcRouterKind { @@ -319,15 +239,6 @@ pub enum VpcRouterKind { Custom, } -impl From for VpcRouterKind { - fn from(kind: model::VpcRouterKind) -> Self { - match kind { - model::VpcRouterKind::Custom => Self::Custom, - model::VpcRouterKind::System => Self::System, - } - } -} - /// A VPC router defines a series of rules that indicate where traffic /// should be sent depending on its destination. #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] @@ -342,16 +253,6 @@ pub struct VpcRouter { pub vpc_id: Uuid, } -impl From for VpcRouter { - fn from(router: model::VpcRouter) -> Self { - Self { - identity: router.identity(), - vpc_id: router.vpc_id, - kind: router.kind.into(), - } - } -} - // IP POOLS #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] @@ -361,12 +262,6 @@ pub struct IpPool { pub project_id: Option, } -impl From for IpPool { - fn from(pool: model::IpPool) -> Self { - Self { identity: pool.identity(), project_id: pool.project_id } - } -} - #[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema)] pub struct IpPoolRange { pub id: Uuid, @@ -374,16 +269,6 @@ pub struct IpPoolRange { pub range: IpRange, } -impl From for IpPoolRange { - fn from(range: model::IpPoolRange) -> Self { - Self { - id: range.id, - time_created: range.time_created, - range: IpRange::from(&range), - } - } -} - // INSTANCE EXTERNAL IP ADDRESSES #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] @@ -402,12 +287,6 @@ pub struct Rack { pub identity: AssetIdentityMetadata, } -impl From for Rack { - fn from(rack: model::Rack) -> Self { - Self { identity: AssetIdentityMetadata::from(&rack) } - } -} - // SLEDS /// Client view of an [`Sled`] @@ -418,15 +297,6 @@ pub struct Sled { pub service_address: SocketAddrV6, } -impl From for Sled { - fn from(sled: model::Sled) -> Self { - Self { - identity: AssetIdentityMetadata::from(&sled), - service_address: sled.address(), - } - } -} - // SILO USERS /// Client view of a [`User`] @@ -437,16 +307,6 @@ pub struct User { pub display_name: String, } -impl From for User { - fn from(user: model::SiloUser) -> Self { - Self { - id: user.id(), - // TODO the use of external_id as display_name is temporary - display_name: user.external_id, - } - } -} - // BUILT-IN USERS /// Client view of a [`UserBuiltin`] @@ -458,12 +318,6 @@ pub struct UserBuiltin { pub identity: IdentityMetadata, } -impl From for UserBuiltin { - fn from(user: model::UserBuiltin) -> Self { - Self { identity: user.identity() } - } -} - // ROLES /// Client view of a [`Role`] @@ -473,15 +327,6 @@ pub struct Role { pub description: String, } -impl From for Role { - fn from(role: model::RoleBuiltin) -> Self { - Self { - name: RoleName::new(&role.resource_type, &role.role_name), - description: role.description, - } - } -} - // SSH KEYS /// Client view of a [`SshKey`] @@ -497,16 +342,6 @@ pub struct SshKey { pub public_key: String, } -impl From for SshKey { - fn from(ssh_key: model::SshKey) -> Self { - Self { - identity: ssh_key.identity(), - silo_user_id: ssh_key.silo_user_id, - public_key: ssh_key.public_key, - } - } -} - // OAUTH 2.0 DEVICE AUTHORIZATION REQUESTS & TOKENS /// Response to an initial device authorization request. @@ -532,26 +367,6 @@ pub struct DeviceAuthResponse { pub expires_in: u16, } -impl DeviceAuthResponse { - // We need the host to construct absolute verification URIs. - pub fn from_model(model: model::DeviceAuthRequest, host: &str) -> Self { - Self { - // TODO-security: use HTTPS - verification_uri: format!("http://{}/device/verify", host), - verification_uri_complete: format!( - "http://{}/device/verify?user_code={}", - host, &model.user_code - ), - user_code: model.user_code, - device_code: model.device_code, - expires_in: model - .time_expires - .signed_duration_since(model.time_created) - .num_seconds() as u16, - } - } -} - /// Successful access token grant. See RFC 6749 ยง5.1. /// TODO-security: `expires_in`, `refresh_token`, etc. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] @@ -563,15 +378,6 @@ pub struct DeviceAccessTokenGrant { pub token_type: DeviceAccessTokenType, } -impl From for DeviceAccessTokenGrant { - fn from(access_token: model::DeviceAccessToken) -> Self { - Self { - access_token: format!("oxide-token-{}", access_token.token), - token_type: DeviceAccessTokenType::Bearer, - } - } -} - /// The kind of token granted. #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] #[serde(rename_all = "snake_case")] diff --git a/nexus/src/db/identity.rs b/nexus/types/src/identity.rs similarity index 90% rename from nexus/src/db/identity.rs rename to nexus/types/src/identity.rs index ca7b1817dfd..b226d56a32a 100644 --- a/nexus/src/db/identity.rs +++ b/nexus/types/src/identity.rs @@ -6,9 +6,9 @@ // Copyright 2021 Oxide Computer Company -use super::model::Name; use chrono::{DateTime, Utc}; -use omicron_common::api::external; +use omicron_common::api::external::IdentityMetadata; +use omicron_common::api::external::Name; use uuid::Uuid; /// Identity-related accessors for resources. @@ -28,8 +28,8 @@ pub trait Resource { fn time_modified(&self) -> DateTime; fn time_deleted(&self) -> Option>; - fn identity(&self) -> external::IdentityMetadata { - external::IdentityMetadata { + fn identity(&self) -> IdentityMetadata { + IdentityMetadata { id: self.id(), name: self.name().clone().into(), description: self.description().to_string(), diff --git a/nexus/types/src/lib.rs b/nexus/types/src/lib.rs new file mode 100644 index 00000000000..9ffbc5dc7c9 --- /dev/null +++ b/nexus/types/src/lib.rs @@ -0,0 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +pub mod external_api; +pub mod identity;