diff --git a/src/actors/users_actor.rs b/src/actors/users_actor.rs index 7ed067e..330991e 100644 --- a/src/actors/users_actor.rs +++ b/src/actors/users_actor.rs @@ -1,5 +1,6 @@ use std::net::IpAddr; +use crate::data::provider::ProviderID; use actix::{Actor, Context, Handler, Message, MessageResult}; use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID}; @@ -97,6 +98,7 @@ pub struct Clear2FALoginHistory(pub UserID); #[derive(Eq, PartialEq, Debug, Clone)] pub struct AuthorizedAuthenticationSources { pub local: bool, + pub upstream: Vec, } #[derive(Message)] diff --git a/src/controllers/admin_controller.rs b/src/controllers/admin_controller.rs index 5624fbe..3a2ca63 100644 --- a/src/controllers/admin_controller.rs +++ b/src/controllers/admin_controller.rs @@ -12,7 +12,7 @@ use crate::controllers::settings_controller::BaseSettingsPage; use crate::data::action_logger::{Action, ActionLogger}; use crate::data::client::{Client, ClientID, ClientManager}; use crate::data::current_user::CurrentUser; -use crate::data::provider::{Provider, ProvidersManager}; +use crate::data::provider::{Provider, ProviderID, ProvidersManager}; use crate::data::user::{GeneralSettings, GrantedClients, User, UserID}; use crate::utils::string_utils::rand_str; @@ -43,6 +43,7 @@ struct EditUserTemplate { _p: BaseSettingsPage, u: User, clients: Vec, + providers: Vec, } pub async fn clients_route( @@ -85,6 +86,7 @@ pub struct UpdateUserQuery { two_factor_exemption_after_successful_login: Option, admin: Option, allow_local_login: Option, + authorized_sources: String, grant_type: String, granted_clients: String, two_factor: String, @@ -162,6 +164,10 @@ pub async fn users_route( // Update the list of authorized authentication sources let auth_sources = AuthorizedAuthenticationSources { local: update.0.allow_local_login.is_some(), + upstream: match update.0.authorized_sources.as_str() { + "" => vec![], + s => s.split(',').map(|s| ProviderID(s.to_string())).collect(), + }, }; if edited_user.authorized_authentication_sources() != auth_sources { @@ -282,6 +288,7 @@ pub async fn users_route( pub async fn create_user( admin: CurrentUser, clients: web::Data>, + providers: web::Data>, ) -> impl Responder { let user = User { authorized_clients: Some( @@ -299,6 +306,7 @@ pub async fn create_user( _p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None), u: user, clients: clients.cloned(), + providers: providers.cloned(), } .render() .unwrap(), @@ -313,6 +321,7 @@ pub struct EditUserQuery { pub async fn edit_user( admin: CurrentUser, clients: web::Data>, + providers: web::Data>, users: web::Data>, query: web::Query, ) -> impl Responder { @@ -335,6 +344,7 @@ pub async fn edit_user( ), u: edited_account.unwrap_or_default(), clients: clients.cloned(), + providers: providers.cloned(), } .render() .unwrap(), diff --git a/src/data/user.rs b/src/data/user.rs index 917a079..c3aafb0 100644 --- a/src/data/user.rs +++ b/src/data/user.rs @@ -1,10 +1,11 @@ -use crate::actors::users_actor::AuthorizedAuthenticationSources; use std::collections::HashMap; use std::net::IpAddr; +use crate::actors::users_actor::AuthorizedAuthenticationSources; use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN; use crate::data::client::{Client, ClientID}; use crate::data::login_redirect::LoginRedirect; +use crate::data::provider::{Provider, ProviderID}; use crate::data::totp_key::TotpKey; use crate::data::webauthn_manager::WebauthnPubKey; use crate::utils::time::{fmt_time, time}; @@ -151,6 +152,10 @@ pub struct User { /// Authorize connection through local login #[serde(default = "default_true")] pub allow_local_login: bool, + + /// Allowed third party providers + #[serde(default)] + pub allow_login_from_providers: Vec, } impl User { @@ -175,9 +180,15 @@ impl User { pub fn authorized_authentication_sources(&self) -> AuthorizedAuthenticationSources { AuthorizedAuthenticationSources { local: self.allow_local_login, + upstream: self.allow_login_from_providers.clone(), } } + /// Check if a user can authenticate using a givne provider or not + pub fn can_login_from_provider(&self, provider: &Provider) -> bool { + self.allow_login_from_providers.contains(&provider.id) + } + pub fn granted_clients(&self) -> GrantedClients { match self.authorized_clients.as_deref() { None => GrantedClients::AllClients, @@ -313,6 +324,7 @@ impl Default for User { last_successful_2fa: Default::default(), authorized_clients: Some(Vec::new()), allow_local_login: true, + allow_login_from_providers: vec![], } } } diff --git a/src/data/users_file_entity.rs b/src/data/users_file_entity.rs index bc4d850..4cf9e34 100644 --- a/src/data/users_file_entity.rs +++ b/src/data/users_file_entity.rs @@ -150,6 +150,7 @@ impl UsersSyncBackend for EntityManager { ) -> Res { self.update_user(id, |mut user| { user.allow_local_login = sources.local; + user.allow_login_from_providers = sources.upstream; user }) } diff --git a/templates/settings/edit_user.html b/templates/settings/edit_user.html index 98ae901..88fe58b 100644 --- a/templates/settings/edit_user.html +++ b/templates/settings/edit_user.html @@ -126,6 +126,8 @@
Authorized authentication sources + +
@@ -133,6 +135,20 @@ Allow local login
+ + + + {% for prov in providers %} +
+ + +
+ {% endfor %}
@@ -233,6 +249,13 @@ form.addEventListener("submit", (ev) => { ev.preventDefault(); + const authorized_sources = [...document.querySelectorAll(".authorized_provider")] + .filter(e => e.checked) + .map(e => e.getAttribute("data-id")).join(",") + + document.querySelector("input[name=authorized_sources]").value = authorized_sources; + + const authorized_clients = [...document.querySelectorAll(".authorize_client_checkbox")] .filter(e => e.checked) .map(e => e.getAttribute("data-id")).join(",") @@ -250,6 +273,8 @@ }); + + {% endblock content %} \ No newline at end of file