Add authentication from upstream providers #107

Merged
pierre merged 25 commits from feat-upstream-providers into master 2023-04-27 10:10:29 +00:00
5 changed files with 52 additions and 2 deletions
Showing only changes of commit abd86ff22d - Show all commits

View File

@ -1,5 +1,6 @@
use std::net::IpAddr; use std::net::IpAddr;
use crate::data::provider::ProviderID;
use actix::{Actor, Context, Handler, Message, MessageResult}; use actix::{Actor, Context, Handler, Message, MessageResult};
use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID}; use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID};
@ -97,6 +98,7 @@ pub struct Clear2FALoginHistory(pub UserID);
#[derive(Eq, PartialEq, Debug, Clone)] #[derive(Eq, PartialEq, Debug, Clone)]
pub struct AuthorizedAuthenticationSources { pub struct AuthorizedAuthenticationSources {
pub local: bool, pub local: bool,
pub upstream: Vec<ProviderID>,
} }
#[derive(Message)] #[derive(Message)]

View File

@ -12,7 +12,7 @@ use crate::controllers::settings_controller::BaseSettingsPage;
use crate::data::action_logger::{Action, ActionLogger}; use crate::data::action_logger::{Action, ActionLogger};
use crate::data::client::{Client, ClientID, ClientManager}; use crate::data::client::{Client, ClientID, ClientManager};
use crate::data::current_user::CurrentUser; 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::data::user::{GeneralSettings, GrantedClients, User, UserID};
use crate::utils::string_utils::rand_str; use crate::utils::string_utils::rand_str;
@ -43,6 +43,7 @@ struct EditUserTemplate {
_p: BaseSettingsPage, _p: BaseSettingsPage,
u: User, u: User,
clients: Vec<Client>, clients: Vec<Client>,
providers: Vec<Provider>,
} }
pub async fn clients_route( pub async fn clients_route(
@ -85,6 +86,7 @@ pub struct UpdateUserQuery {
two_factor_exemption_after_successful_login: Option<String>, two_factor_exemption_after_successful_login: Option<String>,
admin: Option<String>, admin: Option<String>,
allow_local_login: Option<String>, allow_local_login: Option<String>,
authorized_sources: String,
grant_type: String, grant_type: String,
granted_clients: String, granted_clients: String,
two_factor: String, two_factor: String,
@ -162,6 +164,10 @@ pub async fn users_route(
// Update the list of authorized authentication sources // Update the list of authorized authentication sources
let auth_sources = AuthorizedAuthenticationSources { let auth_sources = AuthorizedAuthenticationSources {
local: update.0.allow_local_login.is_some(), 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 { if edited_user.authorized_authentication_sources() != auth_sources {
@ -282,6 +288,7 @@ pub async fn users_route(
pub async fn create_user( pub async fn create_user(
admin: CurrentUser, admin: CurrentUser,
clients: web::Data<Arc<ClientManager>>, clients: web::Data<Arc<ClientManager>>,
providers: web::Data<Arc<ProvidersManager>>,
) -> impl Responder { ) -> impl Responder {
let user = User { let user = User {
authorized_clients: Some( authorized_clients: Some(
@ -299,6 +306,7 @@ pub async fn create_user(
_p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None), _p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
u: user, u: user,
clients: clients.cloned(), clients: clients.cloned(),
providers: providers.cloned(),
} }
.render() .render()
.unwrap(), .unwrap(),
@ -313,6 +321,7 @@ pub struct EditUserQuery {
pub async fn edit_user( pub async fn edit_user(
admin: CurrentUser, admin: CurrentUser,
clients: web::Data<Arc<ClientManager>>, clients: web::Data<Arc<ClientManager>>,
providers: web::Data<Arc<ProvidersManager>>,
users: web::Data<Addr<UsersActor>>, users: web::Data<Addr<UsersActor>>,
query: web::Query<EditUserQuery>, query: web::Query<EditUserQuery>,
) -> impl Responder { ) -> impl Responder {
@ -335,6 +344,7 @@ pub async fn edit_user(
), ),
u: edited_account.unwrap_or_default(), u: edited_account.unwrap_or_default(),
clients: clients.cloned(), clients: clients.cloned(),
providers: providers.cloned(),
} }
.render() .render()
.unwrap(), .unwrap(),

View File

@ -1,10 +1,11 @@
use crate::actors::users_actor::AuthorizedAuthenticationSources;
use std::collections::HashMap; use std::collections::HashMap;
use std::net::IpAddr; use std::net::IpAddr;
use crate::actors::users_actor::AuthorizedAuthenticationSources;
use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN; use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN;
use crate::data::client::{Client, ClientID}; use crate::data::client::{Client, ClientID};
use crate::data::login_redirect::LoginRedirect; use crate::data::login_redirect::LoginRedirect;
use crate::data::provider::{Provider, ProviderID};
use crate::data::totp_key::TotpKey; use crate::data::totp_key::TotpKey;
use crate::data::webauthn_manager::WebauthnPubKey; use crate::data::webauthn_manager::WebauthnPubKey;
use crate::utils::time::{fmt_time, time}; use crate::utils::time::{fmt_time, time};
@ -151,6 +152,10 @@ pub struct User {
/// Authorize connection through local login /// Authorize connection through local login
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub allow_local_login: bool, pub allow_local_login: bool,
/// Allowed third party providers
#[serde(default)]
pub allow_login_from_providers: Vec<ProviderID>,
} }
impl User { impl User {
@ -175,9 +180,15 @@ impl User {
pub fn authorized_authentication_sources(&self) -> AuthorizedAuthenticationSources { pub fn authorized_authentication_sources(&self) -> AuthorizedAuthenticationSources {
AuthorizedAuthenticationSources { AuthorizedAuthenticationSources {
local: self.allow_local_login, 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 { pub fn granted_clients(&self) -> GrantedClients {
match self.authorized_clients.as_deref() { match self.authorized_clients.as_deref() {
None => GrantedClients::AllClients, None => GrantedClients::AllClients,
@ -313,6 +324,7 @@ impl Default for User {
last_successful_2fa: Default::default(), last_successful_2fa: Default::default(),
authorized_clients: Some(Vec::new()), authorized_clients: Some(Vec::new()),
allow_local_login: true, allow_local_login: true,
allow_login_from_providers: vec![],
} }
} }
} }

View File

@ -150,6 +150,7 @@ impl UsersSyncBackend for EntityManager<User> {
) -> Res { ) -> Res {
self.update_user(id, |mut user| { self.update_user(id, |mut user| {
user.allow_local_login = sources.local; user.allow_local_login = sources.local;
user.allow_login_from_providers = sources.upstream;
user user
}) })
} }

View File

@ -126,6 +126,8 @@
<!-- Authorized authentication sources --> <!-- Authorized authentication sources -->
<fieldset class="form-group"> <fieldset class="form-group">
<legend class="mt-4">Authorized authentication sources</legend> <legend class="mt-4">Authorized authentication sources</legend>
<!-- Local login -->
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="allow_local_login" id="allow_local_login" <input class="form-check-input" type="checkbox" name="allow_local_login" id="allow_local_login"
{% if u.allow_local_login %} checked="" {% endif %}> {% if u.allow_local_login %} checked="" {% endif %}>
@ -133,6 +135,20 @@
Allow local login Allow local login
</label> </label>
</div> </div>
<!-- Provider -->
<input type="hidden" name="authorized_sources" id="authorized_sources"/>
{% for prov in providers %}
<div class="form-check">
<input class="form-check-input authorized_provider" type="checkbox" name="prov-{{ prov.id.0 }}"
id="prov-{{ prov.id.0 }}"
data-id="{{ prov.id.0 }}"
{% if u.can_login_from_provider(prov) %} checked="" {% endif %}>
<label class="form-check-label" for="prov-{{ prov.id.0 }}">
Allow login from {{ prov.name }}
</label>
</div>
{% endfor %}
</fieldset> </fieldset>
<!-- Granted clients --> <!-- Granted clients -->
@ -233,6 +249,13 @@
form.addEventListener("submit", (ev) => { form.addEventListener("submit", (ev) => {
ev.preventDefault(); 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")] const authorized_clients = [...document.querySelectorAll(".authorize_client_checkbox")]
.filter(e => e.checked) .filter(e => e.checked)
.map(e => e.getAttribute("data-id")).join(",") .map(e => e.getAttribute("data-id")).join(",")
@ -250,6 +273,8 @@
}); });
</script> </script>
{% endblock content %} {% endblock content %}