Content-Length: 621690 | pFad | https://github.com/sebadob/rauthy/commit/b48552e79f2a3aca0c5cefcc25ef7d9f7c21c6d4

91 Merge pull request #256 from sebadob/dyn-client-reg-1 · sebadob/rauthy@b48552e · GitHub
Skip to content

Commit

Permalink
Merge pull request #256 from sebadob/dyn-client-reg-1
Browse files Browse the repository at this point in the history
Dynamic Client Registration
  • Loading branch information
sebadob authored Jan 30, 2024
2 parents 40f841e + cd993e9 commit b48552e
Show file tree
Hide file tree
Showing 17 changed files with 759 additions and 77 deletions.
135 changes: 68 additions & 67 deletions Cargo.lock

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions dev_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

## CURRENT WORK

### Dynamic Client Registration TODO

https://openid.net/specs/openid-connect-registration-1_0.html

- [x] define new POST request registration object by RFC
- [x] implement registration endpoint
- [x] config variable for token-protected dynamic registration
- [x] DB migrations and create `clients_dyn` table with all necessary information
- [ ] implement an efficient way to auto-delete unused clients to prevent spam and bots
- [ ] config var for an auto-cleanup cron job for dyn clients
- [x] implement a GET endpoint specific for dynamic clients in the correct format by RFC
- [x] issue `registration_token`s
- [x] implement a PUT endpoint for clients to self-modify
- [ ] some kind of rate-limiting for an open dyn client reg endpoint
- [x] add `ClientDyn` to secret migrations task to properly migrate `registration_token`s
- [ ] check for dynamic client during final token creation and efficiently update `last_used` in `clients_dyn`
with internal rate limiting
- [ ] change the GET `/clients` to:
- not cache the result -> only used in Admin UI and may grow very big because of dyn client reg
- return a "Simple" response like the `/users` does to not get into issues with many clients

## TODO next features

- respect `login_hint` in the authorize ui
Expand Down
3 changes: 2 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,14 @@ docker-buildx-setup:
# Starts maildev (https://github.com/maildev/maildev) on your localhost for E-Mail testing
mailcrab-start:
#!/usr/bin/env bash
docker run -d --rm -p 1080:1080 -p 1025:1025 --name mailcrab marlonb/mailcrab
docker run -d -p 1080:1080 -p 1025:1025 --name mailcrab --restart unless-stopped marlonb/mailcrab

# Stops maildev
mailcrab-stop:
#!/usr/bin/env bash
docker stop mailcrab
docker rm mailcrab

# Just uses `cargo fmt --all`
Expand Down
16 changes: 16 additions & 0 deletions migrations/postgres/17_dynamic_clients.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
create table clients_dyn
(
id varchar not null
constraint clients_dyn_pk
primary key
constraint clients_dyn_clients_id_fk
references clients
on update cascade on delete cascade,
created bigint not null,
last_used bigint,
registration_token bytea not null,
token_endpoint_auth_method varchar not null
);

create index clients_dyn_last_used_index
on clients_dyn (last_used);
16 changes: 16 additions & 0 deletions migrations/sqlite/17_dynamic_clients.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
create table clients_dyn
(
id text not null
constraint clients_dyn_pk
primary key
constraint clients_dyn_clients_id_fk
references clients
on update cascade on delete cascade,
created integer not null,
last_used integer,
registration_token blob not null,
token_endpoint_auth_method text not null
);

create index clients_dyn_last_used_index
on clients_dyn (last_used);
15 changes: 15 additions & 0 deletions rauthy-common/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ lazy_static! {
pub static ref RE_URI: Regex = Regex::new(r"^[a-zA-Z0-9,.:/_\-&?=~#!$'()*+%]+$").unwrap();
pub static ref RE_USER_NAME: Regex = Regex::new(r"^[a-zA-Z0-9À-ÿ-\s]{2,32}$").unwrap();
pub static ref RE_TOKEN_68: Regex = Regex::new(r"^[a-zA-Z0-9-._~+/]+=*$").unwrap();
pub static ref RE_TOKEN_ENDPOINT_AUTH_METHOD: Regex = Regex::new(r"^(client_secret_post|client_secret_basic|none)$").unwrap();

pub static ref PUB_URL: String = env::var("PUB_URL").expect("PUB_URL env var is not set");
pub static ref PUB_URL_WITH_SCHEME: String = {
Expand All @@ -126,6 +127,20 @@ lazy_static! {
.parse::<bool>()
.unwrap_or(true);

pub static ref ENABLE_DYN_CLIENT_REG: bool = env::var("ENABLE_DYN_CLIENT_REG")
.unwrap_or_else(|_| String::from("false"))
.parse::<bool>()
.expect("ENABLE_DYN_CLIENT_REG cannot be parsed to bool - bad format");
pub static ref DYN_CLIENT_REG_TOKEN: Option<String> = env::var("DYN_CLIENT_REG_TOKEN").ok();
pub static ref DYN_CLIENT_DEFAULT_TOKEN_LIFETIME: i32 = env::var("DYN_CLIENT_DEFAULT_TOKEN_LIFETIME")
.unwrap_or_else(|_| String::from("1800"))
.parse::<i32>()
.expect("DYN_CLIENT_DEFAULT_TOKEN_LIFETIME cannot be parsed to i64 - bad format");
pub static ref DYN_CLIENT_SECRET_AUTO_ROTATE: bool = env::var("DYN_CLIENT_SECRET_AUTO_ROTATE")
.unwrap_or_else(|_| String::from("true"))
.parse::<bool>()
.expect("DYN_CLIENT_SECRET_AUTO_ROTATE cannot be parsed to bool - bad format");

pub static ref ENABLE_EPHEMERAL_CLIENTS: bool = env::var("ENABLE_EPHEMERAL_CLIENTS")
.unwrap_or_else(|_| String::from("false"))
.parse::<bool>()
Expand Down
9 changes: 8 additions & 1 deletion rauthy-common/src/error_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub enum ErrorResponseType {
SessionTimeout,
TooManyRequests(i64),
Unauthorized,
WWWAuthenticate(String),
}

impl Display for ErrorResponseType {
Expand Down Expand Up @@ -96,7 +97,8 @@ impl ResponseError for ErrorResponse {
| ErrorResponseType::PasswordExpired
| ErrorResponseType::SessionExpired
| ErrorResponseType::SessionTimeout
| ErrorResponseType::Unauthorized => StatusCode::UNAUTHORIZED,
| ErrorResponseType::Unauthorized
| ErrorResponseType::WWWAuthenticate(_) => StatusCode::UNAUTHORIZED,
ErrorResponseType::TooManyRequests(_not_before_timestamp) => {
StatusCode::TOO_MANY_REQUESTS
}
Expand Down Expand Up @@ -150,6 +152,11 @@ impl ResponseError for ErrorResponse {
}
}

ErrorResponseType::WWWAuthenticate(msg) => HttpResponseBuilder::new(status)
.insert_header((WWW_AUTHENTICATE, msg.as_str()))
.content_type(APPLICATION_JSON)
.body(serde_json::to_string(self).unwrap()),

_ => {
if status == StatusCode::UNAUTHORIZED {
HttpResponseBuilder::new(status)
Expand Down
123 changes: 120 additions & 3 deletions rauthy-handlers/src/clients.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use crate::ReqPrincipal;
use actix_web::{delete, get, post, put, web, HttpResponse};
use actix_web::http::header::{ACCESS_CONTROL_ALLOW_ORIGIN, WWW_AUTHENTICATE};
use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse};
use rauthy_common::constants::{DYN_CLIENT_REG_TOKEN, ENABLE_DYN_CLIENT_REG};
use rauthy_common::error_response::ErrorResponse;
use rauthy_models::app_state::AppState;
use rauthy_models::entity::api_keys::{AccessGroup, AccessRights};
use rauthy_models::entity::clients::Client;
use rauthy_models::entity::clients_dyn::ClientDyn;
use rauthy_models::entity::colors::ColorEntity;
use rauthy_models::request::{ColorsRequest, NewClientRequest, UpdateClientRequest};
use rauthy_models::response::ClientResponse;
use rauthy_models::request::{
ColorsRequest, DynamicClientRequest, NewClientRequest, UpdateClientRequest,
};
use rauthy_models::response::{ClientResponse, DynamicClientResponse};
use rauthy_service::auth::get_bearer_token_from_header;
use rauthy_service::client;

//github.com/ Returns all existing OIDC clients with all their information, except for the client secrets.
Expand Down Expand Up @@ -131,6 +137,117 @@ pub async fn post_clients(
.map(|r| HttpResponse::Ok().json(ClientResponse::from(r)))
}

//github.com/ OIDC Dynamic Client Registration (if enabled)
#[utoipa::path(
post,
path = "/clients_dyn",
tag = "clients",
request_body = DynamicClientRequest,
responses(
(status = 201, description = "Created", body = DynamicClientResponse),
(status = 400, description = "BadRequest"),
(status = 401, description = "Unauthorized"),
(status = 404, description = "NotFound"),
),
)]
#[post("/clients_dyn")]
pub async fn post_clients_dyn(
data: web::Data<AppState>,
payload: actix_web_validator::Json<DynamicClientRequest>,
req: HttpRequest,
) -> Result<HttpResponse, ErrorResponse> {
if !*ENABLE_DYN_CLIENT_REG {
return Ok(HttpResponse::NotFound().finish());
}

if let Some(token) = &*DYN_CLIENT_REG_TOKEN {
let bearer = get_bearer_token_from_header(req.headers())?;
if token != &bearer {
return Ok(HttpResponse::Unauthorized()
.insert_header((
WWW_AUTHENTICATE,
r#"error="invalid_token",
error_description="Invalid registration access token"#,
))
.finish());
}
}

Client::create_dynamic(&data, payload.into_inner())
.await
.map(|resp| {
HttpResponse::Created()
// The registration should be possible from another Web UI by RFC
.insert_header((ACCESS_CONTROL_ALLOW_ORIGIN, "*"))
.json(resp)
})
}

//github.com/ GET a dynamic OIDC client
#[utoipa::path(
get,
path = "/clients_dyn/{id}",
tag = "clients",
responses(
(status = 200, description = "Ok", body = DynamicClientResponse),
(status = 400, description = "BadRequest"),
(status = 401, description = "Unauthorized"),
(status = 404, description = "NotFound"),
),
)]
#[get("/clients_dyn/{id}")]
pub async fn get_clients_dyn(
data: web::Data<AppState>,
id: web::Path<String>,
req: HttpRequest,
) -> Result<HttpResponse, ErrorResponse> {
if !*ENABLE_DYN_CLIENT_REG {
return Ok(HttpResponse::NotFound().finish());
}

let bearer = get_bearer_token_from_header(req.headers())?;
let id = id.into_inner();
let client_dyn = ClientDyn::find(&data, id.clone()).await?;
client_dyn.validate_token(&bearer)?;

let client = Client::find(&data, id).await?;
let resp = DynamicClientResponse::build(&data, client, client_dyn, false);
Ok(HttpResponse::Ok().json(resp))
}

//github.com/ Update a dynamic OIDC client
#[utoipa::path(
put,
path = "/clients_dyn/{id}",
tag = "clients",
request_body = DynamicClientRequest,
responses(
(status = 200, description = "Ok", body = DynamicClientResponse),
(status = 400, description = "BadRequest"),
(status = 401, description = "Unauthorized"),
(status = 404, description = "NotFound"),
),
)]
#[put("/clients_dyn/{id}")]
pub async fn put_clients_dyn(
data: web::Data<AppState>,
payload: actix_web_validator::Json<DynamicClientRequest>,
id: web::Path<String>,
req: HttpRequest,
) -> Result<HttpResponse, ErrorResponse> {
if !*ENABLE_DYN_CLIENT_REG {
return Ok(HttpResponse::NotFound().finish());
}

let bearer = get_bearer_token_from_header(req.headers())?;
let id = id.into_inner();
let client_dyn = ClientDyn::find(&data, id.clone()).await?;
client_dyn.validate_token(&bearer)?;

let resp = Client::update_dynamic(&data, payload.into_inner(), client_dyn).await?;
Ok(HttpResponse::Ok().json(resp))
}

//github.com/ Modifies an OIDC client
//github.com/
//github.com/ **Permissions**
Expand Down
2 changes: 2 additions & 0 deletions rauthy-handlers/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ use utoipa::{openapi, OpenApi};
request::MfaAwaitRequest,
request::MfaPurpose,
request::NewClientRequest,
request::DynamicClientRequest,
request::NewGroupRequest,
request::PasswordHashTimesRequest,
request::PasswordPolicyRequest,
Expand Down Expand Up @@ -192,6 +193,7 @@ use utoipa::{openapi, OpenApi};
response::BlacklistedIp,
response::LoginTimeResponse,
response::ClientResponse,
response::DynamicClientResponse,
response::ClientSecretResponse,
response::EncKeysResponse,
response::HealthResponse,
Expand Down
3 changes: 3 additions & 0 deletions rauthy-main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ async fn actix_main(app_state: web::Data<AppState>) -> std::io::Result<()> {
.service(clients::put_clients)
.service(clients::put_generate_client_secret)
.service(clients::delete_client)
.service(clients::post_clients_dyn)
.service(clients::get_clients_dyn)
.service(clients::put_clients_dyn)
.service(generic::get_login_time)
.service(users::get_users)
.service(users::get_users_register)
Expand Down
Loading

0 comments on commit b48552e

Please sign in to comment.








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/sebadob/rauthy/commit/b48552e79f2a3aca0c5cefcc25ef7d9f7c21c6d4

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy