Add authentication from upstream providers #107
@ -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)]
|
||||||
|
@ -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(),
|
||||||
|
@ -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![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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 %}
|
Loading…
Reference in New Issue
Block a user