Skip to content

82 external oauth roles #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea/
**/target
oadr*.yaml
oadr*.yaml
*swp
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Additionally, the [client](https://crates.io/crates/openleadr-client), [server](
and [common data types](https://crates.io/crates/openleadr-wire) are published to crates.io
and have documentation available on docs.rs.
As an addition, [#17](https://github.com/OpenLEADR/openleadr-rs/issues/17) aims
to produce a detailed OpenAPI specification of the VTN API we provide.
to produce a detailed OpenAPI specification of the VTN API we provide.

## Getting started

Expand All @@ -50,7 +50,7 @@ docker compose up -d # start all other containers, i.e., the VTN
Afterward, the VTN should be reachable at `http://localhost:3000`.

For a more detailed guide,
please refer to the Readmes in the [`./openleadr-client`](./openleadr-client) and
please refer to the Readmes in the [`./openleadr-client`](./openleadr-client) and
[`./openleadr-vtn`](./openleadr-vtn) directories.

## Supported features
Expand All @@ -62,7 +62,7 @@ While we currently do not plan to add this ourselves, we warmly welcome any cont
See the [Contributing section](#contributing) if you are interested.

At the moment, the VTN implements its own OAuth provider,
but we plan to allow for a third-party OAuth provider as well,
but we plan to allow for a third-party OAuth provider as well,
see [#26](https://github.com/openLEADR/openleadr-rs/issues/26).

The client and server do support creating, retrieving, updating,
Expand All @@ -76,7 +76,7 @@ Again, we warmly welcome contributions or sponsoring if you are interested in ad

The VEN is a library for conveniently interacting with the REST API provided by a VTN.
We aim for a clean and easy-to-understand API of the library to be used by business or VEN logic.
Additionally, we will use the library to create a CLI application for easy testing and prototyping,
Additionally, we will use the library to create a CLI application for easy testing and prototyping,
see [#52](https://github.com/OpenLEADR/openleadr-rs/issues/52) for the current progress.

## Testing
Expand Down Expand Up @@ -116,7 +116,7 @@ See also [Supported features](#supported-features).
## Contributing
We expect you to follow our [code of conduct](CODE_OF_CONDUCT.md) for any contribution.

If you are missing a feature or see unexpected behavior,
If you are missing a feature or see unexpected behavior,
do not hesitate to open an issue on our [GitHub](https://github.com/OpenLEADR/openleadr-rs) page.
If you suspect a security-critical issue, please refer to [`SECURITY.md`](SECURITY.md).

Expand All @@ -128,7 +128,7 @@ as the [LF energy contribution guidelines](https://tac.lfenergy.org/process/cont
By doing so, you acknowledge the text in [`CONTRIBUTING`](CONTRIBUTING).
The easiest way is to add a `-s` flag to the `git commit` command, i.e. use `git commit -s`.

If you are interested in contributing but don't know where to start,
If you are interested in contributing but don't know where to start,
check out issues marked as [good first issue](https://github.com/OpenLEADR/openleadr-rs/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
or [help wanted](https://github.com/OpenLEADR/openleadr-rs/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22),
or simply open an issue and ask for good starting points.
Expand All @@ -145,7 +145,7 @@ It is mainly a thin layer to abstract the HTTP interaction with the VTN.
Thus, if your application is written in another language than Rust,
it is most likely less work
to write a small HTTP abstraction layer yourself
than using a language interoperability layer on top of our client library.
than using a language interoperability layer on top of our client library.

## Help us make an impact: We're seeking funding!

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ services:
interval: 5s
timeout: 5s
retries: 5
ports:
ports:
- ${PG_PORT}:5432
volumes:
- database-data:/var/lib/postgresql/data/
Expand Down
7 changes: 6 additions & 1 deletion openleadr-client/src/bin/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ use openleadr_wire::program::ProgramContent;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = openleadr_client::Client::with_url(
let reqwest_client = reqwest::Client::new();

let client = openleadr_client::Client::with_details(
"http://localhost:3000/".try_into()?,
"http://localhost:8080/token".try_into()?,
reqwest_client,
Some(ClientCredentials::new(
"admin".to_string(),
"admin".to_string(),
)),
);

let _created_program = client.create_program(ProgramContent::new("name")).await?;
// let created_program_1 = client.create_program(ProgramContent::new("name1")).await?;
// let program = client.get_program_by_name("name").await?;
Expand Down
2 changes: 1 addition & 1 deletion openleadr-client/src/bin/everest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

tokio::spawn(async move {
while let Some(enforced_limits) = output_receiver.recv().await {
eprintln!("received by mock everest: {:?}", enforced_limits);
eprintln!("received by mock everest: {enforced_limits:?}");
}
});

Expand Down
10 changes: 5 additions & 5 deletions openleadr-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ impl From<openleadr_wire::oauth::OAuthError> for Error {
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::Reqwest(err) => write!(f, "Reqwest error: {}", err),
Error::Serde(err) => write!(f, "Serde error: {}", err),
Error::UrlParseError(err) => write!(f, "URL parse error: {}", err),
Error::Problem(err) => write!(f, "OpenADR Problem: {:?}", err),
Error::AuthProblem(err) => write!(f, "Authentication problem: {:?}", err),
Error::Reqwest(err) => write!(f, "Reqwest error: {err}"),
Error::Serde(err) => write!(f, "Serde error: {err}"),
Error::UrlParseError(err) => write!(f, "URL parse error: {err}"),
Error::Problem(err) => write!(f, "OpenADR Problem: {err:?}"),
Error::AuthProblem(err) => write!(f, "Authentication problem: {err:?}"),
Error::ObjectNotFound => write!(f, "Object not found"),
Error::DuplicateObject => write!(f, "Found more than one object matching the filter"),
Error::InvalidParentObject => write!(f, "Invalid parent object"),
Expand Down
4 changes: 2 additions & 2 deletions openleadr-client/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ async fn local_vtn_test_client(db: PgPool, auth_role: AuthRole) -> TestContext {
let cred = default_credentials(auth_role);
let storage = PostgresStorage::new(db).unwrap();

let router = AppState::new(storage).into_router();
let router = AppState::new(storage).await.into_router();
TestContext {
client: MockClientRef::new(router).into_client(Some(cred)),
}
Expand All @@ -120,7 +120,7 @@ pub async fn setup_mock_client(db: PgPool) -> Client {
let storage = PostgresStorage::new(db).unwrap();
// storage.auth.try_write().unwrap().push(auth_info);

let app_state = AppState::new(storage);
let app_state = AppState::new(storage).await;

MockClientRef::new(app_state.into_router()).into_client(Some(client_credentials))
}
Expand Down
6 changes: 3 additions & 3 deletions openleadr-vtn/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

![LF energy OpenLEADR logo](../openleadr-logo.svg)

This crate contains an OpenADR VTN implementation.
This crate contains an OpenADR VTN implementation.

The following contains information specific to the VTN application, i.e., the server.
If you are interested in information about the whole project, please visit the [project level Readme](../README.md).
Expand Down Expand Up @@ -53,15 +53,15 @@ The VTN implementation does feature an implementation of an OAuth provider inclu
to allow for an easy setup.
The OpenADR specification does not require this feature but mentions that there must exist some OAuth provider somewhere.
Generally, the idea of OAuth is to decouple the authorization from the resource server, here the VTN.
Therefore, the OAuth provider feature is optional.
Therefore, the OAuth provider feature is optional.
You can either disable it during compile time or runtime.

**During runtime**
The OAuth configuration of the VTN is done via the following environment variables:
- `OAUTH_TYPE` (allowed values: `INTERNAL`, `EXTERNAL`. Defaults to `INTERNAL`)
- `OAUTH_BASE64_SECRET` (must be at least 256 bit long. Required if `OAUTH_KEY_TYPE` is `HMAC`)
- `OAUTH_KEY_TYPE`(allows values: `HMAC`, `RSA`, `EC`, `ED`. Defaults to `HMAC`)
- `OAUTH_PEM` (path to a PEM encoded public key file. Required for all `OAUTH_KEY_TYPE`s, except `HMAC`)
- `OAUTH_JWKS_LOCATION` (path to the OAUTH server well known JWKS endpoint. Required for all `OAUTH_KEY_TYPE`s, except `HMAC`)
- `OAUTH_VALID_AUDIENCES` (specifies the list of valid audiences for token validation, ensuring that the token is intended for the correct recipient. Required when `OAUTH_TYPE` is `EXTERNAL`. Optional and defaults to an empty list when `OAUTH_TYPE` is `INTERNAL`, which will fail validation if an `aud` claim is present in the decoded access token.)

The internal OAuth provider does only support `HMAC`.
Expand Down
2 changes: 2 additions & 0 deletions openleadr-vtn/src/api/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use axum::{
use axum_extra::headers::{authorization::Bearer, Header};
use openleadr_wire::oauth::{OAuthError, OAuthErrorType};
use reqwest::header;

#[cfg(feature = "internal-oauth")]
use tracing::trace;

#[derive(Debug, Deserialize, Validate)]
Expand Down
32 changes: 16 additions & 16 deletions openleadr-vtn/src/api/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ mod test {
Request::builder()
.method(method)
.uri(format!("/events/{}", event.id))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(serde_json::to_vec(&event).unwrap()))
.unwrap()
Expand All @@ -177,15 +177,15 @@ mod test {
assert_eq!(events[events.len() - 1].content, event)
}

(AppState::new(store), events)
(AppState::new(store).await, events)
}

async fn get_help(id: &str, token: &str, app: &mut Router) -> Response<Body> {
app.oneshot(
Request::builder()
.method(http::Method::GET)
.uri(format!("/events/{}", id))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.uri(format!("/events/{id}"))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.body(Body::empty())
.unwrap(),
)
Expand Down Expand Up @@ -236,7 +236,7 @@ mod test {

let request = Request::builder()
.method(http::Method::DELETE)
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.uri(format!("/events/{event_id}"))
.body(Body::empty())
.unwrap();
Expand Down Expand Up @@ -292,7 +292,7 @@ mod test {
let request = Request::builder()
.method(http::Method::POST)
.uri("/events")
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(serde_json::to_vec(content).unwrap()))
.unwrap();
Expand Down Expand Up @@ -328,7 +328,7 @@ mod test {
let request = Request::builder()
.method(http::Method::GET)
.uri(format!("/events?{query_params}"))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::empty())
.unwrap();
Expand Down Expand Up @@ -371,7 +371,7 @@ mod test {
..default_event_content()
};

let test = ApiTest::new(db, vec![AuthRole::AnyBusiness]);
let test = ApiTest::new(db, vec![AuthRole::AnyBusiness]).await;

for event in vec![event1, event2, event3] {
let (status, _) = test
Expand Down Expand Up @@ -480,7 +480,7 @@ mod test {
#[ignore = "Depends on https://github.com/oadr3-org/openadr3-vtn-reference-implementation/issues/104"]
#[sqlx::test]
async fn name_constraint_validation(db: PgPool) {
let test = ApiTest::new(db, vec![AuthRole::AnyBusiness]);
let test = ApiTest::new(db, vec![AuthRole::AnyBusiness]).await;

let events = [
EventContent {
Expand Down Expand Up @@ -532,7 +532,7 @@ mod test {

#[sqlx::test(fixtures("programs"))]
async fn ordered_by_priority(db: PgPool) {
let test = ApiTest::new(db, vec![AuthRole::AnyBusiness]);
let test = ApiTest::new(db, vec![AuthRole::AnyBusiness]).await;

let events = vec![
EventContent {
Expand Down Expand Up @@ -623,7 +623,7 @@ mod test {
Request::builder()
.method(http::Method::DELETE)
.uri(format!("/events/{}", "event-3"))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.body(Body::empty())
.unwrap(),
)
Expand All @@ -638,7 +638,7 @@ mod test {
Request::builder()
.method(http::Method::PUT)
.uri(format!("/events/{}", "event-3"))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(serde_json::to_vec(&content).unwrap()))
.unwrap(),
Expand All @@ -654,7 +654,7 @@ mod test {
Request::builder()
.method(http::Method::PUT)
.uri(format!("/events/{}", "event-3"))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(serde_json::to_vec(&content).unwrap()))
.unwrap(),
Expand All @@ -670,7 +670,7 @@ mod test {
Request::builder()
.method(http::Method::DELETE)
.uri(format!("/events/{}", "event-3"))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.body(Body::empty())
.unwrap(),
)
Expand Down Expand Up @@ -840,7 +840,7 @@ mod test {
Request::builder()
.method(http::Method::DELETE)
.uri(format!("/events/{}", "event-3"))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.body(Body::empty())
.unwrap(),
)
Expand All @@ -854,7 +854,7 @@ mod test {
Request::builder()
.method(http::Method::PUT)
.uri(format!("/events/{}", "event-3"))
.header(http::header::AUTHORIZATION, format!("Bearer {}", token))
.header(http::header::AUTHORIZATION, format!("Bearer {token}"))
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(
serde_json::to_vec(&default_event_content()).unwrap(),
Expand Down
15 changes: 8 additions & 7 deletions openleadr-vtn/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ mod test {
}

impl ApiTest {
pub(crate) fn new(db: PgPool, roles: Vec<AuthRole>) -> Self {
pub(crate) async fn new(db: PgPool, roles: Vec<AuthRole>) -> Self {
let store = PostgresStorage::new(db).unwrap();
let app_state = AppState::new(store);
let app_state = AppState::new(store).await;

let token = app_state
.jwt_manager
Expand Down Expand Up @@ -257,15 +257,16 @@ mod test {

pub(crate) async fn state(db: PgPool) -> AppState {
let store = PostgresStorage::new(db).unwrap();
AppState::new(store)
AppState::new(store).await
}

#[sqlx::test]
async fn unsupported_media_type(db: PgPool) {
let mut test = ApiTest::new(
db.clone(),
vec![AuthRole::AnyBusiness, AuthRole::UserManager],
);
)
.await;

let response = (&mut test.router)
.oneshot(
Expand Down Expand Up @@ -293,7 +294,7 @@ mod test {

#[sqlx::test]
async fn method_not_allowed(db: PgPool) {
let test = ApiTest::new(db.clone(), vec![]);
let test = ApiTest::new(db.clone(), vec![]).await;

let (status, _) = test
.request::<Problem>(Method::DELETE, "/programs", Body::empty())
Expand All @@ -304,7 +305,7 @@ mod test {

#[sqlx::test]
async fn not_found(db: PgPool) {
let test = ApiTest::new(db.clone(), vec![AuthRole::VenManager]);
let test = ApiTest::new(db.clone(), vec![AuthRole::VenManager]).await;

let (status, _) = test
.request::<Problem>(Method::GET, "/not-existent", Body::empty())
Expand All @@ -314,7 +315,7 @@ mod test {

#[sqlx::test]
async fn healthcheck(db: PgPool) {
let test = ApiTest::new(db.clone(), vec![]);
let test = ApiTest::new(db.clone(), vec![]).await;

let status = test.empty_request(Method::GET, "/health").await;
assert_eq!(status, StatusCode::OK);
Expand Down
Loading
Loading