Compare commits

...

4 Commits

Author SHA1 Message Date
d27c542e1f Can grant a client to all users
Some checks failed
continuous-integration/drone/push Build is failing
2023-04-15 10:39:22 +02:00
412eaf2bff Update 2023-04-15 10:33:06 +02:00
eed9bcdf8b Can specify default clients 2023-04-15 10:31:11 +02:00
14bda544f7 Update all dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-15 10:15:35 +02:00
8 changed files with 444 additions and 316 deletions

693
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ serde = { version = "1.0.159", features = ["derive"] }
bcrypt = "0.14.0" bcrypt = "0.14.0"
uuid = { version = "1.2.2", features = ["v4"] } uuid = { version = "1.2.2", features = ["v4"] }
mime_guess = "2.0.4" mime_guess = "2.0.4"
askama = "0.11.1" askama = "0.12.0"
futures-util = "0.3.27" futures-util = "0.3.27"
urlencoding = "2.1.2" urlencoding = "2.1.2"
rand = "0.8.5" rand = "0.8.5"

View File

@@ -16,6 +16,10 @@ You can configure a list of clients (Relying Parties) in a `clients.yaml` file w
description: Git with a cup of tea description: Git with a cup of tea
secret: TOP_SECRET secret: TOP_SECRET
redirect_uri: https://mygit.mywebsite.com/ redirect_uri: https://mygit.mywebsite.com/
# If you want new accounts to be granted access to this client by default
default: true
# If you want the client to be granted to every users, regardless their account configuration
granted_to_all_users: true
``` ```
On the first run, BasicOIDC will create a new administrator with credentials `admin` / `admin`. On first login you will have to change these default credentials. On the first run, BasicOIDC will create a new administrator with credentials `admin` / `admin`. On first login you will have to change these default credentials.

View File

@@ -65,7 +65,7 @@ pub struct UpdateUserQuery {
} }
pub async fn users_route( pub async fn users_route(
user: CurrentUser, admin: CurrentUser,
users: web::Data<Addr<UsersActor>>, users: web::Data<Addr<UsersActor>>,
update_query: Option<web::Form<UpdateUserQuery>>, update_query: Option<web::Form<UpdateUserQuery>>,
logger: ActionLogger, logger: ActionLogger,
@@ -225,7 +225,7 @@ pub async fn users_route(
HttpResponse::Ok().body( HttpResponse::Ok().body(
UsersListTemplate { UsersListTemplate {
_p: BaseSettingsPage::get("Users list", &user, danger, success), _p: BaseSettingsPage::get("Users list", &admin, danger, success),
users, users,
} }
.render() .render()
@@ -233,11 +233,20 @@ pub async fn users_route(
) )
} }
pub async fn create_user(user: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder { pub async fn create_user(admin: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder {
let mut user = User::default();
user.authorized_clients = Some(
clients
.get_default_clients()
.iter()
.map(|u| u.id.clone())
.collect(),
);
HttpResponse::Ok().body( HttpResponse::Ok().body(
EditUserTemplate { EditUserTemplate {
_p: BaseSettingsPage::get("Create a new user", user.deref(), None, None), _p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
u: Default::default(), u: user,
clients: clients.cloned(), clients: clients.cloned(),
} }
.render() .render()
@@ -251,7 +260,7 @@ pub struct EditUserQuery {
} }
pub async fn edit_user( pub async fn edit_user(
user: CurrentUser, admin: CurrentUser,
clients: web::Data<ClientManager>, clients: web::Data<ClientManager>,
users: web::Data<Addr<UsersActor>>, users: web::Data<Addr<UsersActor>>,
query: web::Query<EditUserQuery>, query: web::Query<EditUserQuery>,
@@ -266,7 +275,7 @@ pub async fn edit_user(
EditUserTemplate { EditUserTemplate {
_p: BaseSettingsPage::get( _p: BaseSettingsPage::get(
"Edit user account", "Edit user account",
user.deref(), admin.deref(),
match edited_account.is_none() { match edited_account.is_none() {
true => Some("Could not find requested user!".to_string()), true => Some("Could not find requested user!".to_string()),
false => None, false => None,

View File

@@ -164,7 +164,7 @@ pub async fn authorize(
}; };
// Check if user is authorized to access the application // Check if user is authorized to access the application
if !user.can_access_app(&client.id) { if !user.can_access_app(&client) {
return error_redirect( return error_redirect(
&query, &query,
"invalid_request", "invalid_request",

View File

@@ -6,11 +6,28 @@ pub struct ClientID(pub String);
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Client { pub struct Client {
/// The ID of the client
pub id: ClientID, pub id: ClientID,
/// The human-readable name of the client
pub name: String, pub name: String,
/// A short description of the service provided by the client
pub description: String, pub description: String,
/// The secret used by the client to retrieve authenticated users information
pub secret: String, pub secret: String,
/// The URI where the users should be redirected once authenticated
pub redirect_uri: String, pub redirect_uri: String,
/// Specify if the client must be allowed by default for new account
#[serde(default = "bool::default")]
pub default: bool,
/// Specify whether a client is granted to all users
#[serde(default = "bool::default")]
pub granted_to_all_users: bool,
} }
impl PartialEq for Client { impl PartialEq for Client {
@@ -33,6 +50,13 @@ impl EntityManager<Client> {
None None
} }
/// Get the list of default clients.
///
/// i.e. the clients that are granted to new accounts by default
pub fn get_default_clients(&self) -> Vec<&Client> {
self.iter().filter(|u| u.default).collect()
}
pub fn apply_environment_variables(&mut self) { pub fn apply_environment_variables(&mut self) {
for c in self.iter_mut() { for c in self.iter_mut() {
c.id = ClientID(apply_env_vars(&c.id.0)); c.id = ClientID(apply_env_vars(&c.id.0));

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::net::IpAddr; use std::net::IpAddr;
use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN; use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN;
use crate::data::client::ClientID; use crate::data::client::{Client, ClientID};
use crate::data::login_redirect::LoginRedirect; use crate::data::login_redirect::LoginRedirect;
use crate::data::totp_key::TotpKey; use crate::data::totp_key::TotpKey;
use crate::data::webauthn_manager::WebauthnPubKey; use crate::data::webauthn_manager::WebauthnPubKey;
@@ -170,10 +170,14 @@ impl User {
} }
} }
pub fn can_access_app(&self, id: &ClientID) -> bool { pub fn can_access_app(&self, client: &Client) -> bool {
if client.granted_to_all_users {
return true;
}
match self.granted_clients() { match self.granted_clients() {
GrantedClients::AllClients => true, GrantedClients::AllClients => true,
GrantedClients::SomeClients(c) => c.contains(id), GrantedClients::SomeClients(c) => c.contains(&client.id),
GrantedClients::NoClient => false, GrantedClients::NoClient => false,
} }
} }

View File

@@ -144,7 +144,7 @@
<div class="form-check"> <div class="form-check">
<input id="client-{{ c.id.0 }}" class="form-check-input authorize_client_checkbox" type="checkbox" <input id="client-{{ c.id.0 }}" class="form-check-input authorize_client_checkbox" type="checkbox"
data-id="{{ c.id.0 }}" data-id="{{ c.id.0 }}"
{% if u.can_access_app(c.id) %} checked="" {% endif %}> {% if u.can_access_app(c) %} checked="" {% endif %}>
<label class="form-check-label" for="client-{{ c.id.0 }}"> <label class="form-check-label" for="client-{{ c.id.0 }}">
{{ c.name }} {{ c.name }}
</label> </label>