Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f5a3404
allow importing existing users when the localpart matches in upstream…
mcalinghee Mar 11, 2025
e66d636
Tweak CI for Tchap
MatMaul Apr 10, 2025
52b2c67
update CI for main_tchap
odelcroi Apr 23, 2025
bd6ebd8
add error when email is not allowed
odelcroi Apr 17, 2025
4334452
add EmailAllowedResult to differenciate errors
odelcroi Apr 22, 2025
b16cbc6
add tchap identity server as env var
odelcroi Apr 22, 2025
8afb123
change text
odelcroi Apr 22, 2025
b9ddaf9
remove unused code
odelcroi Apr 22, 2025
845079c
lint
odelcroi Apr 22, 2025
43625f5
clean dep
odelcroi Apr 22, 2025
dcb8e2b
extract identity client
odelcroi Apr 22, 2025
401f784
refactor client
odelcroi Apr 22, 2025
b4fa7b3
rust style
odelcroi Apr 22, 2025
e8a89be
add filters for legacy email and legacy localpart
odelcroi Apr 17, 2025
7a298a3
Merge branch 'add-is-email-allowed-error' into test/register_oidc
odelcroi Apr 23, 2025
a43fbb6
fix cargo dep
odelcroi Apr 23, 2025
8c880a1
Update Cargo.toml
odelcroi Apr 23, 2025
e3ac1fd
Update Cargo.toml
odelcroi Apr 23, 2025
125d86e
Update Cargo.toml
odelcroi Apr 23, 2025
d362f97
Update build.yaml
odelcroi Apr 23, 2025
9d6509e
Update build.yaml
odelcroi Apr 23, 2025
3580c47
Update Cargo.toml
odelcroi Apr 24, 2025
c810d82
Merge remote-tracking branch 'origin/main_tchap' into test/register_oidc
odelcroi Apr 24, 2025
8388dcc
fix unit test
odelcroi Apr 24, 2025
bb846da
add tchap tag
odelcroi Apr 24, 2025
1894c19
rust style
odelcroi Apr 24, 2025
81597f1
fix clippy
odelcroi Apr 24, 2025
c6ce9dc
add comment
odelcroi Apr 24, 2025
c58a1a8
use internal API internal-info
odelcroi Apr 24, 2025
8482cb5
Merge branch 'add-is-email-allowed-error' into test/register_oidc
odelcroi Apr 24, 2025
415a769
Merge branch 'feat/allow_override_user' into test/register_oidc
odelcroi Apr 30, 2025
611e5a8
correct default identity server port
mcalinghee May 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ name: Build
on:
push:
branches:
- main
- main_tchap
- "release/**"
- "test/**"
tags:
- "v*"

Expand All @@ -22,9 +23,9 @@ env:
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
IMAGE: ghcr.io/element-hq/matrix-authentication-service
IMAGE_SYN2MAS: ghcr.io/element-hq/matrix-authentication-service/syn2mas
BUILDCACHE: ghcr.io/element-hq/matrix-authentication-service/buildcache
IMAGE: ghcr.io/tchapgouv/matrix-authentication-service
IMAGE_SYN2MAS: ghcr.io/tchapgouv/matrix-authentication-service/syn2mas
BUILDCACHE: ghcr.io/tchapgouv/matrix-authentication-service/buildcache
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index

jobs:
Expand Down Expand Up @@ -313,7 +314,7 @@ jobs:
# Only sign on tags and on commits on main branch
if: |
github.event_name != 'pull_request'
&& (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
&& (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main_tchap')

env:
REGULAR_DIGEST: ${{ steps.output.outputs.metadata && fromJSON(steps.output.outputs.metadata).regular.digest }}
Expand All @@ -329,7 +330,7 @@ jobs:
syn2mas:
name: Release syn2mas on NPM
runs-on: ubuntu-24.04
if: github.event_name != 'pull_request'
if: 'false'

permissions:
contents: read
Expand Down Expand Up @@ -422,7 +423,7 @@ jobs:

unstable:
name: Update the unstable release
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main_tchap'
runs-on: ubuntu-24.04

needs:
Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ mas-tower = { path = "./crates/tower/", version = "=0.15.0-rc.0" }
oauth2-types = { path = "./crates/oauth2-types/", version = "=0.15.0-rc.0" }
syn2mas = { path = "./crates/syn2mas", version = "=0.15.0-rc.0" }

# :tchap:
# [workspace.dependencies.tchap]
# path = "./crates/tchap"
# version = "=0.1.0"
# :tchap:end


# OpenAPI schema generation and validation
[workspace.dependencies.aide]
version = "0.14.2"
Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ pub async fn config_sync(
fetch_userinfo: provider.fetch_userinfo,
userinfo_signed_response_alg: provider.userinfo_signed_response_alg,
response_mode,
allow_existing_users: provider.allow_existing_users,
additional_authorization_parameters: provider
.additional_authorization_parameters
.into_iter()
Expand Down
7 changes: 7 additions & 0 deletions crates/config/src/sections/upstream_oauth2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,13 @@ pub struct Provider {
#[serde(default, skip_serializing_if = "ClaimsImports::is_default")]
pub claims_imports: ClaimsImports,

/// Whether to allow a user logging in via OIDC to match a pre-existing
/// account instead of failing. This could be used if switching from
/// password logins to OIDC.
//Defaults to false.
#[serde(default)]
pub allow_existing_users: bool,

/// Additional parameters to include in the authorization request
///
/// Orders of the keys are not preserved.
Expand Down
1 change: 1 addition & 0 deletions crates/data-model/src/upstream_oauth2/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ pub struct UpstreamOAuthProvider {
pub created_at: DateTime<Utc>,
pub disabled_at: Option<DateTime<Utc>>,
pub claims_imports: ClaimsImports,
pub allow_existing_users: bool,
pub additional_authorization_parameters: Vec<(String, String)>,
}

Expand Down
5 changes: 5 additions & 0 deletions crates/handlers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ mas-templates.workspace = true
oauth2-types.workspace = true
zxcvbn = "3.1.0"

# tchap.workspace = true
#:tchap:
tchap = { path = "../tchap", version = "=0.1.0" }
#:tchap:end

[dev-dependencies]
insta.workspace = true
tracing-subscriber.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ mod test_utils {
token_endpoint_override: None,
userinfo_endpoint_override: None,
jwks_uri_override: None,
allow_existing_users: true,
additional_authorization_parameters: Vec::new(),
ui_order: 0,
}
Expand Down
1 change: 1 addition & 0 deletions crates/handlers/src/upstream_oauth2/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ mod tests {
created_at: clock.now(),
disabled_at: None,
claims_imports: UpstreamOAuthProviderClaimsImports::default(),
allow_existing_users: false,
additional_authorization_parameters: Vec::new(),
};

Expand Down
100 changes: 89 additions & 11 deletions crates/handlers/src/upstream_oauth2/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ use mas_templates::{
use minijinja::Environment;
use opentelemetry::{Key, KeyValue, metrics::Counter};
use serde::{Deserialize, Serialize};
//:tchap:
use tchap::{self, EmailAllowedResult};
use thiserror::Error;
use tracing::warn;
use ulid::Ulid;

//:tchap: end
use super::{
UpstreamSessionsCookie,
template::{AttributeMappingContext, environment},
Expand Down Expand Up @@ -434,7 +437,53 @@ pub(crate) async fn get(
&context,
provider.claims_imports.email.is_required(),
)? {
Some(value) => ctx.with_email(value, provider.claims_imports.email.is_forced()),
Some(value) => {
//:tchap:
let server_name = homeserver.homeserver();
let email_result = check_email_allowed(&value, &server_name).await;

match email_result {
EmailAllowedResult::Allowed => {
// Email is allowed, continue
}
EmailAllowedResult::WrongServer => {
// Email is mapped to a different server
let ctx = ErrorContext::new()
.with_code("wrong_server")
.with_description(format!(
"Votre adresse mail {} est associée à un autre serveur.",
value
))
.with_details(format!("Veuillez-vous contacter le support de Tchap [email protected]"))
.with_language(&locale);

//return error template
return Ok((
cookie_jar,
Html(templates.render_error(&ctx)?).into_response(),
));
}
EmailAllowedResult::InvitationMissing => {
// Server requires an invitation that is not present
let ctx = ErrorContext::new()
.with_code("invitation_missing")
.with_description(format!(
"Vous avez besoin d'une invitation pour accéder à Tchap."
))
.with_details(format!("Les partenaires externes peuvent accéder à Tchap uniquement avec une invitation d'un agent public."))
.with_language(&locale);

//return error template
return Ok((
cookie_jar,
Html(templates.render_error(&ctx)?).into_response(),
));
}
}
//:tchap: end

ctx.with_email(value, provider.claims_imports.email.is_forced())
}
None => ctx,
}
};
Expand Down Expand Up @@ -465,7 +514,9 @@ pub(crate) async fn get(
.await
.map_err(RouteError::HomeserverConnection)?;

if maybe_existing_user.is_some() || !is_available {
if !provider.allow_existing_users
&& (maybe_existing_user.is_some() || !is_available)
{
if let Some(existing_user) = maybe_existing_user {
// The mapper returned a username which already exists, but isn't
// linked to this upstream user.
Expand Down Expand Up @@ -742,15 +793,16 @@ pub(crate) async fn post(
mas_templates::UpstreamRegisterFormField::Username,
FieldError::Required,
);
} else if repo.user().exists(&username).await? {
} else if !provider.allow_existing_users && repo.user().exists(&username).await? {
form_state.add_error_on_field(
mas_templates::UpstreamRegisterFormField::Username,
FieldError::Exists,
);
} else if !homeserver
.is_localpart_available(&username)
.await
.map_err(RouteError::HomeserverConnection)?
} else if !provider.allow_existing_users
&& !homeserver
.is_localpart_available(&username)
.await
.map_err(RouteError::HomeserverConnection)?
{
// The user already exists on the homeserver
tracing::warn!(
Expand Down Expand Up @@ -830,10 +882,22 @@ pub(crate) async fn post(
.into_response());
}

REGISTRATION_COUNTER.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]);

// Now we can create the user
let user = repo.user().add(&mut rng, &clock, username).await?;
let user = if provider.allow_existing_users {
// If the provider allows existing users, we can use the existing user
let existing_user = repo.user().find_by_username(&username).await?;
if existing_user.is_some() {
existing_user.unwrap()
} else {
REGISTRATION_COUNTER
.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]);
// This case should not happen
repo.user().add(&mut rng, &clock, username).await?
}
} else {
REGISTRATION_COUNTER.add(1, &[KeyValue::new(PROVIDER, provider.id.to_string())]);
// Now we can create the user
repo.user().add(&mut rng, &clock, username).await?
};

if let Some(terms_url) = &site_config.tos_uri {
repo.user_terms()
Expand Down Expand Up @@ -889,6 +953,19 @@ pub(crate) async fn post(
Ok((cookie_jar, post_auth_action.go_next(&url_builder)).into_response())
}

//:tchap:
///real function used when not testing
#[cfg(not(test))]
async fn check_email_allowed(email: &str, server_name: &str) -> EmailAllowedResult {
tchap::is_email_allowed(email, server_name).await
}
///mock function used when testing
#[cfg(test)]
async fn check_email_allowed(_email: &str, _server_name: &str) -> EmailAllowedResult {
EmailAllowedResult::Allowed
}
//:tchap:end

#[cfg(test)]
mod tests {
use hyper::{Request, StatusCode, header::CONTENT_TYPE};
Expand Down Expand Up @@ -975,6 +1052,7 @@ mod tests {
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: None,
allow_existing_users: true,
additional_authorization_parameters: Vec::new(),
ui_order: 0,
},
Expand Down
7 changes: 7 additions & 0 deletions crates/handlers/src/upstream_oauth2/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use minijinja::{
Environment, Error, ErrorKind, Value,
value::{Enumerator, Object},
};
use tchap;

/// Context passed to the attribute mapping template
///
Expand Down Expand Up @@ -187,6 +188,12 @@ pub fn environment() -> Environment<'static> {
env.add_filter("tlvdecode", tlvdecode);
env.add_filter("string", string);
env.add_filter("from_json", from_json);

// Add Tchap-specific filters, this could be a generic config submitted
// to upstream allowing all users to add their own filters without upstream code modifications
// tester les fonctions async pour le reseau
env.add_filter("email_to_display_name", |s: &str| tchap::email_to_display_name(s));
env.add_filter("email_to_mxid_localpart", |s: &str| tchap::email_to_mxid_localpart(s));

env.set_unknown_method_callback(minijinja_contrib::pycompat::unknown_method_callback);

Expand Down
2 changes: 2 additions & 0 deletions crates/handlers/src/views/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ mod test {
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: None,
allow_existing_users: true,
additional_authorization_parameters: Vec::new(),
ui_order: 0,
},
Expand Down Expand Up @@ -535,6 +536,7 @@ mod test {
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: None,
allow_existing_users: true,
additional_authorization_parameters: Vec::new(),
ui_order: 1,
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading