chain of auth
diff --git a/app_dart/bin/gae_server.dart b/app_dart/bin/gae_server.dart index ad41734..187be7e 100644 --- a/app_dart/bin/gae_server.dart +++ b/app_dart/bin/gae_server.dart
@@ -61,17 +61,31 @@ configUpdater.startUpdateLoop(config); final firebaseJwtValidator = FirebaseJwtValidator(cache: cache); - final dashboardAuthProvider = DashboardAuthentication( + + const cronAuthentication = DashboardCronAuthentication(); + final firebaseAuthentication = DashboardFirebaseAuthentication( cache: cache, - firebaseJwtValidator: firebaseJwtValidator, + validator: firebaseJwtValidator, firestore: firestore, ); - final presubmitAuthProvider = PresubmitAuthentication( + final githubAuthentication = GithubAuthentication( cache: cache, config: config, - firebaseJwtValidator: firebaseJwtValidator, - firestore: firestore, + validator: firebaseJwtValidator, ); + + final cronAuthProvider = ChainOfAuthentication.forProviders([ + cronAuthentication, + ]); + final dashboardAuthProvider = ChainOfAuthentication.forProviders([ + cronAuthentication, + firebaseAuthentication, + ]); + final presubmitAuthProvider = ChainOfAuthentication.forProviders([ + firebaseAuthentication, + githubAuthentication, + ]); + final AuthenticationProvider swarmingAuthProvider = SwarmingAuthenticationProvider(config: config); @@ -146,7 +160,7 @@ bigQuery: bigQuery, cache: cache, dashboardAuthProvider: dashboardAuthProvider, - cronAuthProvider: dashboardAuthProvider.onlyCron, + cronAuthProvider: cronAuthProvider, presubmitAuthProvider: presubmitAuthProvider, branchService: branchService, buildBucketClient: buildBucketClient,
diff --git a/app_dart/lib/src/request_handling/dashboard_authentication.dart b/app_dart/lib/src/request_handling/dashboard_authentication.dart index 2987119..38f8e04 100644 --- a/app_dart/lib/src/request_handling/dashboard_authentication.dart +++ b/app_dart/lib/src/request_handling/dashboard_authentication.dart
@@ -15,9 +15,9 @@ import '../service/firebase_jwt_validator.dart'; import 'exceptions.dart'; -/// Class capable of authenticating [Request]s from the Dashboard +/// Class capable of authenticating [Request]s from multiple sources. /// -/// There are two types of authentication this class supports: +/// There are three types of authentication this class can chain: /// /// 1. If the request has the `'X-Appengine-Cron'` HTTP header set to "true", /// then the request will be authenticated as an App Engine cron job. @@ -37,6 +37,9 @@ /// User accounts are only authorized if the user is either a "@google.com" /// account or is an [AllowedAccount] in Cocoon's Firestore. /// +/// 3. If the request has github.com token, then the request will be authenticated +/// as a GitHub user account. +/// /// If none of the above authentication methods yield an authenticated /// request, then the request is unauthenticated, and any call to /// [authenticate] will throw an [Unauthenticated] exception. @@ -45,41 +48,10 @@ /// /// * <https://cloud.google.com/appengine/docs/standard/python/reference/request-response-headers> @immutable -interface class DashboardAuthentication implements AuthenticationProvider { - factory DashboardAuthentication({ - required CacheService cache, - required FirebaseJwtValidator firebaseJwtValidator, - required FirestoreService firestore, - ClientContextProvider clientContextProvider = Providers.serviceScopeContext, - HttpClientProvider httpClientProvider = Providers.freshHttpClient, - }) { - return DashboardAuthentication.forProviders( - authProviders: [ - DashboardCronAuthentication( - clientContextProvider: clientContextProvider, - ), - DashboardFirebaseAuthentication( - cache: cache, - validator: firebaseJwtValidator, - clientContextProvider: clientContextProvider, - firestore: firestore, - ), - ], - ); - } - +interface class ChainOfAuthentication implements AuthenticationProvider { /// Creates an AuthenticationProvider with a chain of responsibility. - DashboardAuthentication.forProviders({ - required List<AuthenticationProvider> authProviders, - }) : _authenticationChain = authProviders; - - /// Returns a version of this authentication with only the cron provider. - DashboardAuthentication get onlyCron { - final cronAuth = _authenticationChain.firstWhere( - (ac) => ac is DashboardCronAuthentication, - ); - return DashboardAuthentication.forProviders(authProviders: [cronAuth]); - } + ChainOfAuthentication.forProviders(List<AuthenticationProvider> authProviders) + : _authenticationChain = authProviders; final List<AuthenticationProvider> _authenticationChain;
diff --git a/app_dart/lib/src/request_handling/presubmit_authentication.dart b/app_dart/lib/src/request_handling/presubmit_authentication.dart index 863e4f7..2e28e1b 100644 --- a/app_dart/lib/src/request_handling/presubmit_authentication.dart +++ b/app_dart/lib/src/request_handling/presubmit_authentication.dart
@@ -17,80 +17,6 @@ import 'exceptions.dart'; /// Class capable of authenticating [Request]s from the Checkrun page. -/// -/// There are two types of authentication this class supports: -/// -/// 1. If the request has the `'X-Flutter-IdToken'` HTTP header -/// set to a valid encrypted JWT token, then the request will be authenticated -/// as a user account. -/// -/// @google.com accounts can call APIs using curl and gcloud. -/// E.g. curl '<api_url>' -H "X-Flutter-IdToken: $(gcloud auth print-identity-token)" -/// -/// User accounts are only authorized if the user is either a "@google.com" -/// account or is an [AllowedAccount] in Cocoon's Firestore. -/// -/// 2. If the request has github.com token, then the request will be authenticated -/// as a GitHub user account. -/// -/// If none of the above authentication methods yield an authenticated -/// request, then the request is unauthenticated, and any call to -/// [authenticate] will throw an [Unauthenticated] exception. -/// -/// See also: -/// -/// * <https://cloud.google.com/appengine/docs/standard/python/reference/request-response-headers> -@immutable -interface class PresubmitAuthentication implements AuthenticationProvider { - PresubmitAuthentication({ - required CacheService cache, - required Config config, - required FirebaseJwtValidator firebaseJwtValidator, - required FirestoreService firestore, - ClientContextProvider clientContextProvider = Providers.serviceScopeContext, - HttpClientProvider httpClientProvider = Providers.freshHttpClient, - }) { - _authenticationChain.addAll([ - DashboardFirebaseAuthentication( - cache: cache, - validator: firebaseJwtValidator, - clientContextProvider: clientContextProvider, - firestore: firestore, - ), - GithubAuthentication( - cache: cache, - config: config, - validator: firebaseJwtValidator, - clientContextProvider: clientContextProvider, - ), - ]); - } - - final _authenticationChain = <AuthenticationProvider>[]; - - /// Authenticates the specified [request] and returns the associated - /// [AuthenticatedContext]. - /// - /// See the class documentation on [AuthenticationProvider] for a discussion - /// of the different types of authentication that are accepted. - /// - /// This will throw an [Unauthenticated] exception if the request is - /// unauthenticated. - @override - Future<AuthenticatedContext> authenticate(Request request) async { - /// Walk through the providers - for (final provider in _authenticationChain) { - try { - return await provider.authenticate(request); - } on Unauthenticated { - // nothing - } - } - throw const Unauthenticated('User is not signed in'); - } -} - -/// Class capable of authenticating [Request]s from the Checkrun page. class GithubAuthentication implements AuthenticationProvider { GithubAuthentication({ required CacheService cache,
diff --git a/app_dart/tool/local_server.dart b/app_dart/tool/local_server.dart index 9d0c0d3..41f12fd 100644 --- a/app_dart/tool/local_server.dart +++ b/app_dart/tool/local_server.dart
@@ -36,17 +36,31 @@ final bigQuery = await BigQueryService.from(const GoogleAuthProvider()); final firebaseJwtValidator = FirebaseJwtValidator(cache: cache); - final dashboardAuthProvider = DashboardAuthentication( + + const cronAuthentication = DashboardCronAuthentication(); + final firebaseAuthentication = DashboardFirebaseAuthentication( cache: cache, - firebaseJwtValidator: firebaseJwtValidator, + validator: firebaseJwtValidator, firestore: firestore, ); - final presubmitAuthProvider = PresubmitAuthentication( + final githubAuthentication = GithubAuthentication( cache: cache, config: config, - firebaseJwtValidator: firebaseJwtValidator, - firestore: firestore, + validator: firebaseJwtValidator, ); + + final cronAuthProvider = ChainOfAuthentication.forProviders([ + cronAuthentication, + ]); + final dashboardAuthProvider = ChainOfAuthentication.forProviders([ + cronAuthentication, + firebaseAuthentication, + ]); + final presubmitAuthProvider = ChainOfAuthentication.forProviders([ + firebaseAuthentication, + githubAuthentication, + ]); + final AuthenticationProvider swarmingAuthProvider = SwarmingAuthenticationProvider(config: config); @@ -114,7 +128,7 @@ bigQuery: bigQuery, cache: cache, dashboardAuthProvider: dashboardAuthProvider, - cronAuthProvider: dashboardAuthProvider.onlyCron, + cronAuthProvider: cronAuthProvider, presubmitAuthProvider: presubmitAuthProvider, branchService: branchService, buildBucketClient: buildBucketClient,
diff --git a/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart b/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart index 506d7be..16bbdae 100644 --- a/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart +++ b/packages/cocoon_integration_test/lib/src/fakes/fake_dashboard_authentication.dart
@@ -6,7 +6,7 @@ import 'package:cocoon_service/src/request_handling/exceptions.dart'; // ignore: must_be_immutable -class FakeDashboardAuthentication implements DashboardAuthentication { +class FakeDashboardAuthentication implements ChainOfAuthentication { FakeDashboardAuthentication({ FakeClientContext? clientContext, this.authenticated = true, @@ -23,9 +23,6 @@ throw const Unauthenticated('Not authenticated'); } } - - @override - DashboardAuthentication get onlyCron => this; } // ignore: must_be_immutable