Skip to content

Commit

Permalink
feat: separate OIDC discovery from .well-known
Browse files Browse the repository at this point in the history
Signed-off-by: The one with the braid <[email protected]>
  • Loading branch information
TheOneWithTheBraid committed Feb 11, 2025
1 parent 590f349 commit 83f4ec1
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@ import 'package:http/http.dart' hide Client;
import 'package:matrix/matrix.dart';

extension OidcProviderMetadataExtension on Client {
/// High-level function to get the OIDC auth metadata for the homeserver
///
/// Performs checks on all three revisions of MSC2965 for OIDC discovery.
///
/// In case the homeserver supports OIDC, this will store the OIDC Auth
/// Metadata provided by the homeserver.
///
/// This function might usually be called by [checkHomeserver]. Works similar
/// to [getWellknown].
Future<Map<String, Object?>?> getOidcDiscoveryInformation() async {
Map<String, Object?>? oidcMetadata;

// MSC2965 no longer expects any information on whether OIDC is supported
// to be present in .well-known - the only way to figure out is sadly
// calling the /auth_metadata endpoint.

try {
oidcMetadata = await getOidcAuthMetadata();
} catch (e) {
Logs().v(
'[OIDC] auth_metadata endpoint not supported. '
'Fallback on legacy .well-known discovery.',
e,
);
}
if (oidcMetadata == null) {
try {
// even though no longer required, a homeserver *might* still prefer
// the fallback on .well-known discovery as per
// https://openid.net/specs/openid-connect-discovery-1_0.html
final issuer =
// ignore: deprecated_member_use_from_same_package
wellKnown?.authentication?.issuer ?? await oidcAuthIssuer();
// ignore: deprecated_member_use_from_same_package
oidcMetadata = await getOidcAuthWellKnown(issuer);
} catch (e) {
Logs().v('[OIDC] Homeserver does not support OIDC delegation.', e);
}
}
if (oidcMetadata == null) {
return null;
}

Logs().v('[OIDC] Found auth metadata document.');

await database?.storeOidcAuthMetadata(oidcMetadata);
return oidcMetadata;
}

/// Loads the Auth Metadata from the homeserver
///
/// Even though homeservers might still use the previous proposed approaches
Expand Down
87 changes: 30 additions & 57 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ class Client extends MatrixApi {
/// the cached .well-known file updated using [getWellknown]
DiscoveryInformation? get wellKnown => _wellKnown;

/// the cached OIDC auth metadata as per MSC 2965 updated using [getWellknown]
/// the cached OIDC auth metadata as per MSC 2965 updated using
/// [getOidcDiscoveryInformation]
Map<String, Object?>? get oidcAuthMetadata => _oidcAuthMetadata;

/// the cached OIDC auth metadata as per MSC 2966
Expand Down Expand Up @@ -558,6 +559,7 @@ class Client extends MatrixApi {
)> checkHomeserver(
Uri homeserverUrl, {
bool checkWellKnown = true,
bool checkOidcDiscovery = true,
Set<String>? overrideSupportedVersions,
}) async {
final supportedVersions =
Expand All @@ -575,15 +577,22 @@ class Client extends MatrixApi {
Logs().v('Found no well known information', e);
}
}
if (checkOidcDiscovery) {
try {
_oidcAuthMetadata = await getOidcDiscoveryInformation();
} catch (e) {
Logs().v('[OIDC] Error checking OIDC discovery', e);
}
}

// Check if server supports at least one supported version
final versions = await getVersions();
if (!versions.versions
.any((version) => supportedVersions.contains(version))) {
Logs().w(
'Server supports the versions: ${versions.toString()} but this application is only compatible with ${supportedVersions.toString()}.',
throw BadServerVersionsException(
versions.versions.toSet(),
supportedVersions,
);
assert(false);
}

final loginTypes = await getLoginFlows() ?? [];
Expand All @@ -610,47 +619,16 @@ class Client extends MatrixApi {
/// Note that this endpoint is not necessarily handled by the homeserver,
/// but by another webserver, to be used for discovering the homeserver URL.
///
/// In case the homeserver supports OIDC, this will also request and store
/// the OIDC Auth Metadata provided by the homeserver.
///
/// The result of this call is stored in [wellKnown] for later use at runtime.
@override
Future<DiscoveryInformation> getWellknown() async {
DiscoveryInformation wellKnown;
try {
wellKnown = await super.getWellknown();
final wellKnown = await super.getWellknown();

// do not reset the well known here, so super call
super.homeserver = wellKnown.mHomeserver.baseUrl.stripTrailingSlash();
_wellKnown = wellKnown;
await database?.storeWellKnown(wellKnown);

// do not reset the well known here, so super call
super.homeserver = wellKnown.mHomeserver.baseUrl.stripTrailingSlash();
_wellKnown = wellKnown;
await database?.storeWellKnown(wellKnown);
} finally {
// MSC2965 no longer expects any information on whether OIDC is supported
// to be present in .well-known - the only way to figure out is sadly
// calling the /auth_metadata endpoint.
try {
try {
_oidcAuthMetadata = await getOidcAuthMetadata();
} on http.ClientException {
Logs().v(
'[OIDC] auth_metadata endpoint not supported. '
'Fallback on legacy .well-known discovery.',
);
// even though no longer required, a homeserver *might* still prefer
// the fallback on .well-known discovery as per
// https://openid.net/specs/openid-connect-discovery-1_0.html
final issuer =
// ignore: deprecated_member_use_from_same_package
_wellKnown?.authentication?.issuer ?? await oidcAuthIssuer();
// ignore: deprecated_member_use_from_same_package
_oidcAuthMetadata = await getOidcAuthWellKnown(issuer);
}
await database?.storeOidcAuthMetadata(_oidcAuthMetadata);
Logs().v('[OIDC] Found auth metadata document.');
} on http.ClientException {
Logs().v('[OIDC] Homeserver does not support OIDC delegation.');
}
}
return wellKnown;
}

Expand Down Expand Up @@ -1704,22 +1682,7 @@ class Client extends MatrixApi {
return pushrules != null ? TryGetPushRule.tryFromJson(pushrules) : null;
}

static const Set<String> supportedVersions = {
'v1.1',
'v1.2',
'v1.3',
'v1.4',
'v1.5',
'v1.6',
'v1.7',
'v1.8',
'v1.9',
'v1.10',
'v1.11',
'v1.12',
'v1.13',
};

static const Set<String> supportedVersions = {'v1.1', 'v1.2'};
static const List<String> supportedDirectEncryptionAlgorithms = [
AlgorithmTypes.olmV1Curve25519AesSha2,
];
Expand Down Expand Up @@ -4091,6 +4054,16 @@ enum SyncStatus {
error,
}

class BadServerVersionsException implements Exception {
final Set<String> serverVersions, supportedVersions;

BadServerVersionsException(this.serverVersions, this.supportedVersions);

@override
String toString() =>
'Server supports the versions: ${serverVersions.toString()} but this application is only compatible with ${supportedVersions.toString()}.';
}

class BadServerLoginTypesException implements Exception {
final Set<String> serverLoginTypes, supportedLoginTypes;

Expand Down

0 comments on commit 83f4ec1

Please sign in to comment.