Skip to content

Commit

Permalink
feat: host openapi spec
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Aug 19, 2022
1 parent 2fc0fef commit 3b46d2e
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 36 deletions.
26 changes: 24 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exclude = [

[patch.crates-io]
#drogue-bazaar = { path = "../drogue-bazaar" }
drogue-bazaar = { git = "https://github.com/drogue-iot/drogue-bazaar", rev = "1a769df7ec04d0b07a7da78f0d91789541bca51b" }
drogue-bazaar = { git = "https://github.com/drogue-iot/drogue-bazaar", rev = "34bc8931b9669e39c52685f2cda2ba9f1b226cb2" }
#drogue-client = { path = "../drogue-client" }
#drogue-client = { git = "https://github.com/drogue-iot/drogue-client", rev = "0cb6998da75905240f06f38a44aac31d7b3fdde5" } # FIXME: awaiting release 0.11.0

Expand Down
3 changes: 3 additions & 0 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ futures = "0.3"
humantime = "2"
humantime-serde = "1"
log = "0.4"
openid = "0.10"
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
serde_yaml = "0.9"
thiserror = "1"
time = "0.1"
tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tracing-actix-web = { version = "0.6", features = ["opentelemetry_0_17"] }
url = "2"

drogue-doppelgaenger-core = { path = "../core" }
70 changes: 39 additions & 31 deletions backend/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,55 @@
use crate::OpenIdClient;
use actix_web::{web, HttpRequest};
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use anyhow::Context;
use drogue_cloud_service_api::endpoints::Endpoints;
use serde_json::{json, Value};
use std::borrow::Cow;
use url::Url;

const SPEC: &str = include_str!("../api/index.yaml");
const SPEC: &str = include_str!("../api/openapi.yaml");

pub fn spec(
req: HttpRequest,
endpoints: &Endpoints,
client: web::Data<OpenIdClient>,
) -> anyhow::Result<Value> {
let mut api: Value = serde_yaml::from_str(SPEC).context("Failed to parse OpenAPI YAML")?;
#[derive(Clone, Debug)]
pub struct OpenApiConfig {
pub authorization_url: Option<Url>,
}

pub async fn api(req: HttpRequest, openapi: web::Data<OpenApiConfig>) -> impl Responder {
match spec(req, openapi.get_ref()) {
Ok(spec) => HttpResponse::Ok().json(spec),
Err(err) => {
log::warn!("Failed to generate OpenAPI spec: {}", err);
HttpResponse::InternalServerError().finish()
}
}
}

fn spec(req: HttpRequest, openapi: &OpenApiConfig) -> anyhow::Result<Value> {
// load API spec

let url = endpoints.api.as_ref().map(Cow::from).unwrap_or_else(|| {
let ci = req.connection_info();
Cow::from(format!("{}://{}", ci.scheme(), ci.host()))
});
let mut api: Value = serde_yaml::from_str(SPEC).context("Failed to parse OpenAPI YAML")?;

// server

api["servers"] = json!([{ "url": url, "description": "Drogue Cloud API" }]);
let ci = req.connection_info();
let url = format!("{}://{}", ci.scheme(), ci.host());
api["servers"] = json!([{ "url": url, "description": "Drogue Doppelgänger API" }]);

// SSO

let url = client.client.config().authorization_endpoint.clone();

api["security"] = json!([{"Drogue Cloud SSO": []}]);
api["components"]["securitySchemes"] = json!({
"Drogue Cloud SSO": {
"type": "oauth2",
"description": "SSO",
"flows": {
"implicit": {
"authorizationUrl": url,
"scopes": {
"openid": "OpenID Connect",
if let Some(url) = &openapi.authorization_url {
api["security"] = json!([{"Drogue Cloud SSO": []}]);
api["components"]["securitySchemes"] = json!({
"Drogue Cloud SSO": {
"type": "oauth2",
"description": "SSO",
"flows": {
"implicit": {
"authorizationUrl": url.to_string(),
"scopes": {
"openid": "OpenID Connect",
}
}
}
}
},
});
},
});
}

// render

Expand Down
36 changes: 34 additions & 2 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
mod api;
mod endpoints;
mod notifier;
mod utils;

use actix_web::web::Json;
use actix_web::{guard, web, Responder};
use crate::api::{api, OpenApiConfig};
use ::openid::Configurable;
use actix_web::{
guard,
web::{self, Json},
Responder,
};
use anyhow::anyhow;
use drogue_bazaar::{
actix::{
auth::{
Expand Down Expand Up @@ -42,6 +49,9 @@ pub struct Config<S: Storage, N: Notifier, Si: Sink, Cmd: CommandSink> {
pub user_auth: Option<ClientConfig>,

pub oauth: openid::AuthenticatorConfig,

#[serde(default)]
pub openapi_oauth_client: Option<String>,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -78,6 +88,22 @@ pub async fn configure<S: Storage, N: Notifier, Si: Sink, Cmd: CommandSink>(
None
};

let authorization_url = match config.openapi_oauth_client {
Some(client) => {
let auth = authenticator.as_ref().ok_or_else(|| {
anyhow!("OpenAPI OAuth is configured, but no OAuth configuration is present")
})?;
let client = auth.client_by_name(&client).ok_or_else(|| {
anyhow!(
"OpenAPI OAuth is configured, but client '{}' could not be found",
client
)
})?;
Some(client.provider.config().authorization_endpoint.clone())
}
None => None,
};

if log::log_enabled!(log::Level::Info) {
log::info!(
"Authentication: {:?}",
Expand All @@ -97,7 +123,13 @@ pub async fn configure<S: Storage, N: Notifier, Si: Sink, Cmd: CommandSink>(
ctx.app_data(service.clone());
ctx.app_data(instance.clone());
ctx.app_data(source.clone());
ctx.app_data(OpenApiConfig {
authorization_url: authorization_url.clone(),
});

ctx.route("/", web::get().to(index));
ctx.route("/api", web::get().to(api));

ctx.service(
web::scope("/api/v1alpha1/things")
.wrap(AuthZ::new(NotAnonymous.or_else_allow()))
Expand Down
24 changes: 24 additions & 0 deletions core/src/injector/mapper/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ pub enum PayloadMapper {
#[serde(default)]
add_timestamp: bool,
},
#[serde(alias = "simpleState")]
SimpleState,
}

#[derive(Clone, Debug, serde::Deserialize)]
pub struct SimpleState {
#[serde(default)]
pub partial: bool,
#[serde(default)]
pub state: BTreeMap<String, Value>,
}

impl Default for PayloadMapper {
Expand All @@ -44,6 +54,7 @@ impl PayloadMapper {
partial,
add_timestamp,
} => self.map_simple(&meta, data, *partial, *add_timestamp),
Self::SimpleState => self.map_simple_state(data),
}
}

Expand Down Expand Up @@ -86,6 +97,19 @@ impl PayloadMapper {
}
}

fn map_simple_state(
&self,
(content_type, schema, data): (Option<String>, Option<Url>, Option<Data>),
) -> anyhow::Result<Message> {
match (content_type.as_deref(), schema, data) {
(Some("application/json" | "text/json"), _, Some(data)) => {
let SimpleState { state, partial } = from_data::<SimpleState>(data)?;
Ok(Message::ReportState { state, partial })
}
(content_type, schema, data) => self.otherwise(content_type, schema, data),
}
}

fn otherwise(
&self,
content_type: Option<&str>,
Expand Down
1 change: 1 addition & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ async fn run(server: Server, startup: &mut dyn Startup) -> anyhow::Result<()> {
listener: server.notifier_source,
oauth,
user_auth: None,
openapi_oauth_client: None,
};

let configurator = drogue_doppelgaenger_backend::configure(startup, backend).await?;
Expand Down

0 comments on commit 3b46d2e

Please sign in to comment.