Can create accounts automatically for a given upstream provider
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		| @@ -1,10 +1,11 @@ | ||||
| use std::net::IpAddr; | ||||
|  | ||||
| use crate::data::provider::{Provider, ProviderID}; | ||||
| use actix::{Actor, Context, Handler, Message, MessageResult}; | ||||
|  | ||||
| use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID}; | ||||
| use crate::utils::err::Res; | ||||
| use crate::utils::string_utils::is_acceptable_login; | ||||
| use actix::{Actor, Context, Handler, Message, MessageResult}; | ||||
| use light_openid::primitives::OpenIDUserInfo; | ||||
|  | ||||
| /// User storage interface | ||||
| pub trait UsersSyncBackend { | ||||
| @@ -38,6 +39,8 @@ pub enum LoginResult { | ||||
|     LocalAuthForbidden, | ||||
|     AuthFromProviderForbidden, | ||||
|     Success(Box<User>), | ||||
|     AccountAutoCreated(Box<User>), | ||||
|     CannotAutoCreateAccount(String), | ||||
| } | ||||
|  | ||||
| #[derive(Message)] | ||||
| @@ -51,6 +54,7 @@ pub struct LocalLoginRequest { | ||||
| #[rtype(LoginResult)] | ||||
| pub struct ProviderLoginRequest { | ||||
|     pub email: String, | ||||
|     pub user_info: OpenIDUserInfo, | ||||
|     pub provider: Provider, | ||||
| } | ||||
|  | ||||
| @@ -187,7 +191,86 @@ impl Handler<ProviderLoginRequest> for UsersActor { | ||||
|                 log::error!("Failed to find user! {e}"); | ||||
|                 MessageResult(LoginResult::Error) | ||||
|             } | ||||
|             Ok(None) => MessageResult(LoginResult::AccountNotFound), | ||||
|             Ok(None) => { | ||||
|                 // Check if automatic account creation is enabled for this provider | ||||
|                 if !msg.provider.allow_auto_account_creation { | ||||
|                     return MessageResult(LoginResult::AccountNotFound); | ||||
|                 } | ||||
|  | ||||
|                 // Extract username for account creation | ||||
|                 let mut username = msg | ||||
|                     .user_info | ||||
|                     .preferred_username | ||||
|                     .unwrap_or(msg.email.to_string()); | ||||
|  | ||||
|                 // Determine username from email, if necessary | ||||
|                 if !is_acceptable_login(&username) | ||||
|                     || matches!( | ||||
|                         self.manager.find_by_username_or_email(&username), | ||||
|                         Ok(Some(_)) | ||||
|                     ) | ||||
|                 { | ||||
|                     username = msg.email.clone(); | ||||
|                 } | ||||
|  | ||||
|                 // Check if username is already taken | ||||
|                 if matches!( | ||||
|                     self.manager.find_by_username_or_email(&username), | ||||
|                     Ok(Some(_)) | ||||
|                 ) { | ||||
|                     return MessageResult(LoginResult::CannotAutoCreateAccount(format!( | ||||
|                         "username {username} is already taken!" | ||||
|                     ))); | ||||
|                 } | ||||
|  | ||||
|                 if !is_acceptable_login(&username) { | ||||
|                     return MessageResult(LoginResult::CannotAutoCreateAccount( | ||||
|                         "could not determine acceptable login for user!".to_string(), | ||||
|                     )); | ||||
|                 } | ||||
|  | ||||
|                 // Automatic account creation | ||||
|                 let user_id = match self.manager.create_user_account(GeneralSettings { | ||||
|                     uid: UserID::random(), | ||||
|                     username, | ||||
|                     first_name: msg.user_info.given_name.unwrap_or_default(), | ||||
|                     last_name: msg.user_info.family_name.unwrap_or_default(), | ||||
|                     email: msg.email.to_string(), | ||||
|                     enabled: true, | ||||
|                     two_factor_exemption_after_successful_login: false, | ||||
|                     is_admin: false, | ||||
|                 }) { | ||||
|                     Ok(u) => u, | ||||
|                     Err(e) => { | ||||
|                         log::error!("Failed to create user account! {e}"); | ||||
|                         return MessageResult(LoginResult::CannotAutoCreateAccount( | ||||
|                             "missing some user information".to_string(), | ||||
|                         )); | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 // Mark the provider as the only authorized source | ||||
|                 if let Err(e) = self.manager.set_authorized_authentication_sources( | ||||
|                     &user_id, | ||||
|                     AuthorizedAuthenticationSources { | ||||
|                         local: false, | ||||
|                         upstream: vec![msg.provider.id], | ||||
|                     }, | ||||
|                 ) { | ||||
|                     log::error!( | ||||
|                         "Failed to set authorized authentication sources for newly created account! {e}" | ||||
|                     ); | ||||
|                 } | ||||
|  | ||||
|                 // Extract user information to return them | ||||
|                 let Ok(Some(user)) = self.manager.find_by_user_id(&user_id) else { | ||||
|                     return MessageResult(LoginResult::CannotAutoCreateAccount( | ||||
|                         "failed to get created user information".to_string(), | ||||
|                     )); | ||||
|                 }; | ||||
|  | ||||
|                 MessageResult(LoginResult::AccountAutoCreated(Box::new(user))) | ||||
|             } | ||||
|             Ok(Some(user)) => { | ||||
|                 if !user.can_login_from_provider(&msg.provider) { | ||||
|                     return MessageResult(LoginResult::AuthFromProviderForbidden); | ||||
|   | ||||
| @@ -1,11 +1,5 @@ | ||||
| use std::sync::Arc; | ||||
|  | ||||
| use actix::Addr; | ||||
| use actix_identity::Identity; | ||||
| use actix_remote_ip::RemoteIP; | ||||
| use actix_web::{HttpRequest, HttpResponse, Responder, web}; | ||||
| use askama::Template; | ||||
|  | ||||
| use crate::actors::bruteforce_actor::BruteForceActor; | ||||
| use crate::actors::providers_states_actor::{ProviderLoginState, ProvidersStatesActor}; | ||||
| use crate::actors::users_actor::{LoginResult, UsersActor}; | ||||
| @@ -18,6 +12,11 @@ use crate::data::login_redirect::LoginRedirect; | ||||
| use crate::data::provider::{ProviderID, ProvidersManager}; | ||||
| use crate::data::provider_configuration::ProviderConfigurationHelper; | ||||
| use crate::data::session_identity::{SessionIdentity, SessionStatus}; | ||||
| use actix::Addr; | ||||
| use actix_identity::Identity; | ||||
| use actix_remote_ip::RemoteIP; | ||||
| use actix_web::{HttpRequest, HttpResponse, Responder, web}; | ||||
| use askama::Template; | ||||
|  | ||||
| #[derive(askama::Template)] | ||||
| #[template(path = "login/prov_login_error.html")] | ||||
| @@ -273,7 +272,7 @@ pub async fn finish_login( | ||||
|     } | ||||
|  | ||||
|     // Check if email was provided by the userinfo endpoint | ||||
|     let email = match user_info.email { | ||||
|     let email = match &user_info.email { | ||||
|         Some(e) => e, | ||||
|         None => { | ||||
|             logger.log(Action::ProviderMissingEmailInResponse { | ||||
| @@ -293,6 +292,7 @@ pub async fn finish_login( | ||||
|     let result: LoginResult = users | ||||
|         .send(users_actor::ProviderLoginRequest { | ||||
|             email: email.clone(), | ||||
|             user_info: user_info.clone(), | ||||
|             provider: provider.clone(), | ||||
|         }) | ||||
|         .await | ||||
| @@ -300,6 +300,13 @@ pub async fn finish_login( | ||||
|  | ||||
|     let user = match result { | ||||
|         LoginResult::Success(u) => u, | ||||
|         LoginResult::AccountAutoCreated(u) => { | ||||
|             logger.log(Action::ProviderAccountAutoCreated { | ||||
|                 provider: &provider, | ||||
|                 user: u.loggable(), | ||||
|             }); | ||||
|             u | ||||
|         } | ||||
|         LoginResult::AccountNotFound => { | ||||
|             logger.log(Action::ProviderAccountNotFound { | ||||
|                 provider: &provider, | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| use actix::Addr; | ||||
| use actix_web::{HttpResponse, Responder, web}; | ||||
| use uuid::Uuid; | ||||
| use webauthn_rs::prelude::RegisterPublicKeyCredential; | ||||
|  | ||||
| use crate::actors::users_actor; | ||||
| @@ -53,7 +52,7 @@ pub async fn save_totp_factor( | ||||
|     } | ||||
|  | ||||
|     let factor = TwoFactor { | ||||
|         id: FactorID(Uuid::new_v4().to_string()), | ||||
|         id: FactorID::random(), | ||||
|         name: factor_name, | ||||
|         kind: TwoFactorType::TOTP(key), | ||||
|     }; | ||||
| @@ -102,7 +101,7 @@ pub async fn save_webauthn_factor( | ||||
|     }; | ||||
|  | ||||
|     let factor = TwoFactor { | ||||
|         id: FactorID(Uuid::new_v4().to_string()), | ||||
|         id: FactorID::random(), | ||||
|         name: factor_name, | ||||
|         kind: TwoFactorType::WEBAUTHN(Box::new(key)), | ||||
|     }; | ||||
|   | ||||
| @@ -150,6 +150,10 @@ pub enum Action<'a> { | ||||
|         provider: &'a Provider, | ||||
|         email: &'a str, | ||||
|     }, | ||||
|     ProviderAccountAutoCreated { | ||||
|         provider: &'a Provider, | ||||
|         user: LoggableUser, | ||||
|     }, | ||||
|     ProviderAccountDisabled { | ||||
|         provider: &'a Provider, | ||||
|         email: &'a str, | ||||
| @@ -282,6 +286,11 @@ impl Action<'_> { | ||||
|                 "could not login using provider {} because the email {email} could not be associated to any account!", | ||||
|                 &provider.id.0 | ||||
|             ), | ||||
|             Action::ProviderAccountAutoCreated { provider, user } => format!( | ||||
|                 "triggered automatic account creation for {} from provider {} because it was not found in local accounts list!", | ||||
|                 user.quick_identity(), | ||||
|                 &provider.id.0 | ||||
|             ), | ||||
|             Action::ProviderAccountDisabled { provider, email } => format!( | ||||
|                 "could not login using provider {} because the account associated to the email {email} is disabled!", | ||||
|                 &provider.id.0 | ||||
|   | ||||
| @@ -26,6 +26,10 @@ pub struct Provider { | ||||
|     /// | ||||
|     /// (.well-known/openid-configuration endpoint) | ||||
|     pub configuration_url: String, | ||||
|  | ||||
|     /// Set to true if accounts on BasicOIDC should be automatically created from this provider | ||||
|     #[serde(default)] | ||||
|     pub allow_auto_account_creation: bool, | ||||
| } | ||||
|  | ||||
| impl Provider { | ||||
|   | ||||
| @@ -14,6 +14,12 @@ use crate::utils::time::{fmt_time, time}; | ||||
| #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Encode, Decode)] | ||||
| pub struct UserID(pub String); | ||||
|  | ||||
| impl UserID { | ||||
|     pub fn random() -> Self { | ||||
|         Self(uuid::Uuid::new_v4().to_string()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct GeneralSettings { | ||||
|     pub uid: UserID, | ||||
| @@ -46,6 +52,12 @@ impl GrantedClients { | ||||
| #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] | ||||
| pub struct FactorID(pub String); | ||||
|  | ||||
| impl FactorID { | ||||
|     pub fn random() -> Self { | ||||
|         Self(uuid::Uuid::new_v4().to_string()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] | ||||
| pub enum TwoFactorType { | ||||
|     TOTP(TotpKey), | ||||
| @@ -295,7 +307,7 @@ impl Eq for User {} | ||||
| impl Default for User { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             uid: UserID(uuid::Uuid::new_v4().to_string()), | ||||
|             uid: UserID::random(), | ||||
|             first_name: "".to_string(), | ||||
|             last_name: "".to_string(), | ||||
|             username: "".to_string(), | ||||
|   | ||||
| @@ -71,7 +71,7 @@ impl UsersSyncBackend for EntityManager<User> { | ||||
|  | ||||
|     fn create_user_account(&mut self, settings: GeneralSettings) -> Res<UserID> { | ||||
|         let mut user = User { | ||||
|             uid: UserID(uuid::Uuid::new_v4().to_string()), | ||||
|             uid: UserID::random(), | ||||
|             ..Default::default() | ||||
|         }; | ||||
|         user.update_general_settings(settings); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user