diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 84920ecb..00000000 --- a/.cirrus.yml +++ /dev/null @@ -1,70 +0,0 @@ -task: - name: Run all unit tests - container: - image: cirrusci/flutter:stable - install_melos_script: - - dart pub global activate melos 2.9.0 - test_script: - - export PATH="$PATH":"$HOME/.pub-cache/bin" - - melos bootstrap - - melos run test:unit --no-select - -task: - name: Build Android example app (stable channel) - container: - image: cirrusci/flutter:stable - pub_cache: - folder: ~/.pub-cache - install_melos_script: - - dart pub global activate melos 2.9.0 - build_script: - - export PATH="$PATH":"$HOME/.pub-cache/bin" - - melos bootstrap - - melos run build:example_android - -task: - name: Build Android example app (3.0.0) - container: - image: cirrusci/flutter:3.0.0 - pub_cache: - folder: ~/.pub-cache - install_melos_script: - - dart pub global activate melos 2.9.0 - build_script: - - export PATH="$PATH":"$HOME/.pub-cache/bin" - - melos bootstrap - - melos run build:example_android - -task: - name: Build iOS example app (stable channel) - macos_instance: - image: ghcr.io/cirruslabs/macos-ventura-xcode:14.2 - pub_cache: - folder: ~/.pub-cache - upgrade_script: - - flutter channel stable - - flutter upgrade - install_melos_script: - - dart pub global activate melos 2.9.0 - build_script: - - export PATH="$PATH":"$HOME/.pub-cache/bin" - - melos bootstrap - - melos run build:example_ios - -task: - name: Build macOS example app (stable channel) - macos_instance: - image: ghcr.io/cirruslabs/macos-ventura-xcode:14.2 - pub_cache: - folder: ~/.pub-cache - upgrade_script: - - flutter channel stable - - flutter upgrade - setup_script: - - flutter config --enable-macos-desktop - install_melos_script: - - dart pub global activate melos 2.9.0 - build_script: - - export PATH="$PATH":"$HOME/.pub-cache/bin" - - melos bootstrap - - melos run build:example_macos diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..048a74fc --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,38 @@ +name: format + +on: push + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + java_format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: axel-op/googlejavaformat-action@v3 + with: + args: '--skip-sorting-imports --replace' + + objc_format: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Test + run: | + which clang-format || brew install clang-format + find . -name '*.m' -exec clang-format -i {} \; + find . -path '*/ios/**/*.h' -exec clang-format -i {} \; + find . -path '*/macos/**/*.h' -exec clang-format -i {} \; + git diff --exit-code || (git commit --all -m "Clang Format" && git push) + + swift_format: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Test + run: | + which swiftlint || brew install swiftlint + swiftlint --fix + git diff --exit-code || (git commit --all -m "Swift Format" && git push) diff --git a/.github/workflows/scripts/install-tools.sh b/.github/workflows/scripts/install-tools.sh new file mode 100755 index 00000000..31760cf0 --- /dev/null +++ b/.github/workflows/scripts/install-tools.sh @@ -0,0 +1,2 @@ +dart pub global activate melos 2.9.0 +melos bootstrap \ No newline at end of file diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 00000000..21783046 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,131 @@ +name: validate + +on: + pull_request: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Run Dart Analyzer + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Analyze + uses: invertase/github-action-dart-analyzer@v1 + with: + fatal-infos: true + fatal-warnings: true + build_example_android_stable: + name: Build Android example app (stable channel) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_android + build_example_android_300: + name: Build Android example app (3.0.0) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: 3.0.0 + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_android + build_example_ios_stable: + name: Build iOS example app (stable channel) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_ios + build_example_ios_300: + name: Build iOS example app (3.0.0) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: 3.0.0 + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_ios + build_example_macos_stable: + name: Build macOS example app (stable channel) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_macos + build_example_macos_300: + name: Build macOS example app (3.0.0) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: 3.0.0 + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_macos + unit_tests: + name: Run all unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Run Tests + run: melos run test:unit --no-select diff --git a/analysis_options.yaml b/analysis_options.yaml index 847ad100..c9e973d7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -28,10 +28,6 @@ analyzer: missing_return: warning # allow having TODOs in the code todo: ignore - # Ignore analyzer hints for updating pubspecs when using Future or - # Stream and not importing dart:async - # Please see https://github.com/flutter/flutter/pull/24528 for details. - sdk_version_async_exported_from_core: ignore exclude: - "bin/cache/**" # the following two are relative to the stocks example and the flutter package respectively @@ -99,12 +95,11 @@ linter: - hash_and_equals - implementation_imports # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 - - iterable_contains_unrelated_type # - join_return_with_assignment # not yet tested - library_names - library_prefixes - lines_longer_than_80_chars - - list_remove_unrelated_type + - collection_methods_unrelated_type # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 - no_adjacent_strings_in_list - no_duplicate_case_values diff --git a/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/FlutterAppauthPlugin.java b/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/FlutterAppauthPlugin.java index dd65e638..b5f426d5 100644 --- a/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/FlutterAppauthPlugin.java +++ b/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/FlutterAppauthPlugin.java @@ -40,609 +40,826 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry; -/** - * FlutterAppauthPlugin - */ -public class FlutterAppauthPlugin implements FlutterPlugin, MethodCallHandler, PluginRegistry.ActivityResultListener, ActivityAware { - private static final String AUTHORIZE_AND_EXCHANGE_CODE_METHOD = "authorizeAndExchangeCode"; - private static final String AUTHORIZE_METHOD = "authorize"; - private static final String TOKEN_METHOD = "token"; - private static final String END_SESSION_METHOD = "endSession"; - - private static final String DISCOVERY_ERROR_CODE = "discovery_failed"; - private static final String AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE = "authorize_and_exchange_code_failed"; - private static final String AUTHORIZE_ERROR_CODE = "authorize_failed"; - private static final String TOKEN_ERROR_CODE = "token_failed"; - private static final String END_SESSION_ERROR_CODE = "end_session_failed"; - private static final String NULL_INTENT_ERROR_CODE = "null_intent"; - private static final String INVALID_CLAIMS_ERROR_CODE = "invalid_claims"; - - private static final String DISCOVERY_ERROR_MESSAGE_FORMAT = "Error retrieving discovery document: [error: %s, description: %s]"; - private static final String TOKEN_ERROR_MESSAGE_FORMAT = "Failed to get token: [error: %s, description: %s]"; - private static final String AUTHORIZE_ERROR_MESSAGE_FORMAT = "Failed to authorize: [error: %s, description: %s]"; - private static final String END_SESSION_ERROR_MESSAGE_FORMAT = "Failed to end session: [error: %s, description: %s]"; - - private static final String NULL_INTENT_ERROR_FORMAT = "Failed to authorize: Null intent received"; - - private final int RC_AUTH_EXCHANGE_CODE = 65030; - private final int RC_AUTH = 65031; - private final int RC_END_SESSION = 65032; - - private Context applicationContext; - private Activity mainActivity; - private PendingOperation pendingOperation; - private String clientSecret; - private boolean allowInsecureConnections; - private AuthorizationService defaultAuthorizationService; - private AuthorizationService insecureAuthorizationService; - - private void setActivity(Activity flutterActivity) { - this.mainActivity = flutterActivity; - } - - private void onAttachedToEngine(Context context, BinaryMessenger binaryMessenger) { - this.applicationContext = context; - defaultAuthorizationService = new AuthorizationService(this.applicationContext); - AppAuthConfiguration.Builder authConfigBuilder = new AppAuthConfiguration.Builder(); - authConfigBuilder.setConnectionBuilder(InsecureConnectionBuilder.INSTANCE); - authConfigBuilder.setSkipIssuerHttpsCheck(true); - insecureAuthorizationService = new AuthorizationService(applicationContext, authConfigBuilder.build()); - final MethodChannel channel = new MethodChannel(binaryMessenger, "crossingthestreams.io/flutter_appauth"); - channel.setMethodCallHandler(this); - } - - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - onAttachedToEngine(binding.getApplicationContext(), binding.getBinaryMessenger()); - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - disposeAuthorizationServices(); - } - - @Override - public void onAttachedToActivity(ActivityPluginBinding binding) { - binding.addActivityResultListener(this); - mainActivity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - this.mainActivity = null; - } - - @Override - public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { - binding.addActivityResultListener(this); - mainActivity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivity() { - this.mainActivity = null; - } - - private void disposeAuthorizationServices() { - defaultAuthorizationService.dispose(); - insecureAuthorizationService.dispose(); - defaultAuthorizationService = null; - insecureAuthorizationService = null; - } - - private void checkAndSetPendingOperation(String method, Result result) { - if (pendingOperation != null) { - throw new IllegalStateException( - "Concurrent operations detected: " + pendingOperation.method + ", " + method); - } - pendingOperation = new PendingOperation(method, result); - } - - - @Override - public void onMethodCall(MethodCall call, @NonNull Result result) { - Map arguments = call.arguments(); - switch (call.method) { - case AUTHORIZE_AND_EXCHANGE_CODE_METHOD: - try { - checkAndSetPendingOperation(call.method, result); - handleAuthorizeMethodCall(arguments, true); - } catch (Exception ex) { - finishWithError(AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); - } - break; - case AUTHORIZE_METHOD: - try { - checkAndSetPendingOperation(call.method, result); - handleAuthorizeMethodCall(arguments, false); - } catch (Exception ex) { - finishWithError(AUTHORIZE_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); - } - break; - case TOKEN_METHOD: - try { - checkAndSetPendingOperation(call.method, result); - handleTokenMethodCall(arguments); - } catch (Exception ex) { - finishWithError(TOKEN_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); - } - break; - case END_SESSION_METHOD: - try { - checkAndSetPendingOperation(call.method, result); - handleEndSessionMethodCall(arguments); - } catch (Exception ex) { - finishWithError(END_SESSION_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); - } - break; - default: - result.notImplemented(); - } - } - - @SuppressWarnings("unchecked") - private AuthorizationTokenRequestParameters processAuthorizationTokenRequestArguments(Map arguments) { - final String clientId = (String) arguments.get("clientId"); - final String issuer = (String) arguments.get("issuer"); - final String discoveryUrl = (String) arguments.get("discoveryUrl"); - final String redirectUrl = (String) arguments.get("redirectUrl"); - final String loginHint = (String) arguments.get("loginHint"); - final String nonce = (String) arguments.get("nonce"); - clientSecret = (String) arguments.get("clientSecret"); - final ArrayList scopes = (ArrayList) arguments.get("scopes"); - final ArrayList promptValues = (ArrayList) arguments.get("promptValues"); - Map serviceConfigurationParameters = (Map) arguments.get("serviceConfiguration"); - Map additionalParameters = (Map) arguments.get("additionalParameters"); - allowInsecureConnections = (boolean) arguments.get("allowInsecureConnections"); - final String responseMode = (String) arguments.get("responseMode"); - - return new AuthorizationTokenRequestParameters(clientId, issuer, discoveryUrl, scopes, redirectUrl, serviceConfigurationParameters, additionalParameters, loginHint, nonce, promptValues, responseMode); - } - - @SuppressWarnings("unchecked") - private TokenRequestParameters processTokenRequestArguments(Map arguments) { - final String clientId = (String) arguments.get("clientId"); - final String issuer = (String) arguments.get("issuer"); - final String discoveryUrl = (String) arguments.get("discoveryUrl"); - final String redirectUrl = (String) arguments.get("redirectUrl"); - final String grantType = (String) arguments.get("grantType"); - clientSecret = (String) arguments.get("clientSecret"); - String refreshToken = null; - if (arguments.containsKey("refreshToken")) { - refreshToken = (String) arguments.get("refreshToken"); - } - String authorizationCode = null; - if (arguments.containsKey("authorizationCode")) { - authorizationCode = (String) arguments.get("authorizationCode"); - } - String codeVerifier = null; - if (arguments.containsKey("codeVerifier")) { - codeVerifier = (String) arguments.get("codeVerifier"); - } - String nonce = null; - if (arguments.containsKey("nonce")) { - nonce = (String) arguments.get("nonce"); - } - final ArrayList scopes = (ArrayList) arguments.get("scopes"); - final Map serviceConfigurationParameters = (Map) arguments.get("serviceConfiguration"); - final Map additionalParameters = (Map) arguments.get("additionalParameters"); - allowInsecureConnections = (boolean) arguments.get("allowInsecureConnections"); - return new TokenRequestParameters(clientId, issuer, discoveryUrl, scopes, redirectUrl, refreshToken, authorizationCode, codeVerifier, nonce, grantType, serviceConfigurationParameters, additionalParameters); - } - - @SuppressWarnings("unchecked") - private EndSessionRequestParameters processEndSessionRequestArguments(Map arguments) { - final String idTokenHint = (String) arguments.get("idTokenHint"); - final String postLogoutRedirectUrl = (String) arguments.get("postLogoutRedirectUrl"); - final String state = (String) arguments.get("state"); - final boolean allowInsecureConnections = (boolean) arguments.get("allowInsecureConnections"); - final String issuer = (String) arguments.get("issuer"); - final String discoveryUrl = (String) arguments.get("discoveryUrl"); - final Map serviceConfigurationParameters = (Map) arguments.get("serviceConfiguration"); - final Map additionalParameters = (Map) arguments.get("additionalParameters"); - return new EndSessionRequestParameters(idTokenHint, postLogoutRedirectUrl, state, issuer, discoveryUrl, allowInsecureConnections, serviceConfigurationParameters, additionalParameters); - } - - private void handleAuthorizeMethodCall(Map arguments, final boolean exchangeCode) { - final AuthorizationTokenRequestParameters tokenRequestParameters = processAuthorizationTokenRequestArguments(arguments); - if (tokenRequestParameters.serviceConfigurationParameters != null) { - AuthorizationServiceConfiguration serviceConfiguration = processServiceConfigurationParameters(tokenRequestParameters.serviceConfigurationParameters); - performAuthorization(serviceConfiguration, tokenRequestParameters.clientId, tokenRequestParameters.redirectUrl, tokenRequestParameters.scopes, tokenRequestParameters.loginHint, tokenRequestParameters.nonce, tokenRequestParameters.additionalParameters, exchangeCode, tokenRequestParameters.promptValues, tokenRequestParameters.responseMode); - } else { - AuthorizationServiceConfiguration.RetrieveConfigurationCallback callback = new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { - @Override - public void onFetchConfigurationCompleted(@Nullable AuthorizationServiceConfiguration serviceConfiguration, @Nullable AuthorizationException ex) { - if (ex == null) { - performAuthorization(serviceConfiguration, tokenRequestParameters.clientId, tokenRequestParameters.redirectUrl, tokenRequestParameters.scopes, tokenRequestParameters.loginHint, tokenRequestParameters.nonce, tokenRequestParameters.additionalParameters, exchangeCode, tokenRequestParameters.promptValues, tokenRequestParameters.responseMode); - } else { - finishWithDiscoveryError(ex); - } - } - }; - if (tokenRequestParameters.discoveryUrl != null) { - AuthorizationServiceConfiguration.fetchFromUrl(Uri.parse(tokenRequestParameters.discoveryUrl), callback, allowInsecureConnections ? InsecureConnectionBuilder.INSTANCE : DefaultConnectionBuilder.INSTANCE); - } else { - AuthorizationServiceConfiguration.fetchFromIssuer(Uri.parse(tokenRequestParameters.issuer), callback, allowInsecureConnections ? InsecureConnectionBuilder.INSTANCE : DefaultConnectionBuilder.INSTANCE); - } - } - } - - private AuthorizationServiceConfiguration processServiceConfigurationParameters(Map serviceConfigurationArguments) { - final String endSessionEndpoint = serviceConfigurationArguments.get("endSessionEndpoint"); - return new AuthorizationServiceConfiguration(Uri.parse(serviceConfigurationArguments.get("authorizationEndpoint")), Uri.parse(serviceConfigurationArguments.get("tokenEndpoint")), null, endSessionEndpoint == null ? null : Uri.parse(endSessionEndpoint)); - } - - private void handleTokenMethodCall(Map arguments) { - final TokenRequestParameters tokenRequestParameters = processTokenRequestArguments(arguments); - if (tokenRequestParameters.serviceConfigurationParameters != null) { - AuthorizationServiceConfiguration serviceConfiguration = processServiceConfigurationParameters(tokenRequestParameters.serviceConfigurationParameters); - performTokenRequest(serviceConfiguration, tokenRequestParameters); - } else { - AuthorizationServiceConfiguration.RetrieveConfigurationCallback callback = new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { - @Override - public void onFetchConfigurationCompleted(@Nullable AuthorizationServiceConfiguration serviceConfiguration, @Nullable AuthorizationException ex) { - if (ex == null) { - performTokenRequest(serviceConfiguration, tokenRequestParameters); - } else { - finishWithDiscoveryError(ex); - } - } - }; - if (tokenRequestParameters.discoveryUrl != null) { - AuthorizationServiceConfiguration.fetchFromUrl(Uri.parse(tokenRequestParameters.discoveryUrl), callback, allowInsecureConnections ? InsecureConnectionBuilder.INSTANCE : DefaultConnectionBuilder.INSTANCE); - } else { - AuthorizationServiceConfiguration.fetchFromIssuer(Uri.parse(tokenRequestParameters.issuer), callback, allowInsecureConnections ? InsecureConnectionBuilder.INSTANCE : DefaultConnectionBuilder.INSTANCE); - } - } - } - - - private void performAuthorization(AuthorizationServiceConfiguration serviceConfiguration, String clientId, String redirectUrl, ArrayList scopes, String loginHint, String nonce, Map additionalParameters, boolean exchangeCode, ArrayList promptValues, String responseMode) { - AuthorizationRequest.Builder authRequestBuilder = - new AuthorizationRequest.Builder( - serviceConfiguration, - clientId, - ResponseTypeValues.CODE, - Uri.parse(redirectUrl)); - - if (scopes != null && !scopes.isEmpty()) { - authRequestBuilder.setScopes(scopes); - } - - if (loginHint != null) { - authRequestBuilder.setLoginHint(loginHint); - } - - if (promptValues != null && !promptValues.isEmpty()) { - authRequestBuilder.setPromptValues(promptValues); - } - - if (responseMode != null) { - authRequestBuilder.setResponseMode(responseMode); - } - - if (nonce != null) { - authRequestBuilder.setNonce(nonce); - } - - if (additionalParameters != null && !additionalParameters.isEmpty()) { - - if(additionalParameters.containsKey("ui_locales")){ - authRequestBuilder.setUiLocales(additionalParameters.get("ui_locales")); - additionalParameters.remove("ui_locales"); - } - - if(additionalParameters.containsKey("claims")){ - try { - final JSONObject claimsAsJson = new JSONObject(additionalParameters.get("claims")); - authRequestBuilder.setClaims(claimsAsJson); - additionalParameters.remove("claims"); - } - catch (JSONException ex) { - finishWithError(INVALID_CLAIMS_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); - return; - } +/** FlutterAppauthPlugin */ +public class FlutterAppauthPlugin + implements FlutterPlugin, + MethodCallHandler, + PluginRegistry.ActivityResultListener, + ActivityAware { + private static final String AUTHORIZE_AND_EXCHANGE_CODE_METHOD = "authorizeAndExchangeCode"; + private static final String AUTHORIZE_METHOD = "authorize"; + private static final String TOKEN_METHOD = "token"; + private static final String END_SESSION_METHOD = "endSession"; + + private static final String DISCOVERY_ERROR_CODE = "discovery_failed"; + private static final String AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE = + "authorize_and_exchange_code_failed"; + private static final String AUTHORIZE_ERROR_CODE = "authorize_failed"; + private static final String TOKEN_ERROR_CODE = "token_failed"; + private static final String END_SESSION_ERROR_CODE = "end_session_failed"; + private static final String NULL_INTENT_ERROR_CODE = "null_intent"; + private static final String INVALID_CLAIMS_ERROR_CODE = "invalid_claims"; + + private static final String DISCOVERY_ERROR_MESSAGE_FORMAT = + "Error retrieving discovery document: [error: %s, description: %s]"; + private static final String TOKEN_ERROR_MESSAGE_FORMAT = + "Failed to get token: [error: %s, description: %s]"; + private static final String AUTHORIZE_ERROR_MESSAGE_FORMAT = + "Failed to authorize: [error: %s, description: %s]"; + private static final String END_SESSION_ERROR_MESSAGE_FORMAT = + "Failed to end session: [error: %s, description: %s]"; + + private static final String NULL_INTENT_ERROR_FORMAT = + "Failed to authorize: Null intent received"; + + private final int RC_AUTH_EXCHANGE_CODE = 65030; + private final int RC_AUTH = 65031; + private final int RC_END_SESSION = 65032; + + private Context applicationContext; + private Activity mainActivity; + private PendingOperation pendingOperation; + private String clientSecret; + private boolean allowInsecureConnections; + private AuthorizationService defaultAuthorizationService; + private AuthorizationService insecureAuthorizationService; + + private void setActivity(Activity flutterActivity) { + this.mainActivity = flutterActivity; + } + + private void onAttachedToEngine(Context context, BinaryMessenger binaryMessenger) { + this.applicationContext = context; + defaultAuthorizationService = new AuthorizationService(this.applicationContext); + AppAuthConfiguration.Builder authConfigBuilder = new AppAuthConfiguration.Builder(); + authConfigBuilder.setConnectionBuilder(InsecureConnectionBuilder.INSTANCE); + authConfigBuilder.setSkipIssuerHttpsCheck(true); + insecureAuthorizationService = + new AuthorizationService(applicationContext, authConfigBuilder.build()); + final MethodChannel channel = + new MethodChannel(binaryMessenger, "crossingthestreams.io/flutter_appauth"); + channel.setMethodCallHandler(this); + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + onAttachedToEngine(binding.getApplicationContext(), binding.getBinaryMessenger()); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + disposeAuthorizationServices(); + } + + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + binding.addActivityResultListener(this); + mainActivity = binding.getActivity(); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + this.mainActivity = null; + } + + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + binding.addActivityResultListener(this); + mainActivity = binding.getActivity(); + } + + @Override + public void onDetachedFromActivity() { + this.mainActivity = null; + } + + private void disposeAuthorizationServices() { + defaultAuthorizationService.dispose(); + insecureAuthorizationService.dispose(); + defaultAuthorizationService = null; + insecureAuthorizationService = null; + } + + private void checkAndSetPendingOperation(String method, Result result) { + if (pendingOperation != null) { + throw new IllegalStateException( + "Concurrent operations detected: " + pendingOperation.method + ", " + method); + } + pendingOperation = new PendingOperation(method, result); + } + + @Override + public void onMethodCall(MethodCall call, @NonNull Result result) { + Map arguments = call.arguments(); + switch (call.method) { + case AUTHORIZE_AND_EXCHANGE_CODE_METHOD: + try { + checkAndSetPendingOperation(call.method, result); + handleAuthorizeMethodCall(arguments, true); + } catch (Exception ex) { + finishWithError( + AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE, + ex.getLocalizedMessage(), + getCauseFromException(ex)); + } + break; + case AUTHORIZE_METHOD: + try { + checkAndSetPendingOperation(call.method, result); + handleAuthorizeMethodCall(arguments, false); + } catch (Exception ex) { + finishWithError( + AUTHORIZE_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); + } + break; + case TOKEN_METHOD: + try { + checkAndSetPendingOperation(call.method, result); + handleTokenMethodCall(arguments); + } catch (Exception ex) { + finishWithError(TOKEN_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); + } + break; + case END_SESSION_METHOD: + try { + checkAndSetPendingOperation(call.method, result); + handleEndSessionMethodCall(arguments); + } catch (Exception ex) { + finishWithError( + END_SESSION_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); + } + break; + default: + result.notImplemented(); + } + } + + @SuppressWarnings("unchecked") + private AuthorizationTokenRequestParameters processAuthorizationTokenRequestArguments( + Map arguments) { + final String clientId = (String) arguments.get("clientId"); + final String issuer = (String) arguments.get("issuer"); + final String discoveryUrl = (String) arguments.get("discoveryUrl"); + final String redirectUrl = (String) arguments.get("redirectUrl"); + final String loginHint = (String) arguments.get("loginHint"); + final String nonce = (String) arguments.get("nonce"); + clientSecret = (String) arguments.get("clientSecret"); + final ArrayList scopes = (ArrayList) arguments.get("scopes"); + final ArrayList promptValues = (ArrayList) arguments.get("promptValues"); + Map serviceConfigurationParameters = + (Map) arguments.get("serviceConfiguration"); + Map additionalParameters = + (Map) arguments.get("additionalParameters"); + allowInsecureConnections = (boolean) arguments.get("allowInsecureConnections"); + final String responseMode = (String) arguments.get("responseMode"); + + return new AuthorizationTokenRequestParameters( + clientId, + issuer, + discoveryUrl, + scopes, + redirectUrl, + serviceConfigurationParameters, + additionalParameters, + loginHint, + nonce, + promptValues, + responseMode); + } + + @SuppressWarnings("unchecked") + private TokenRequestParameters processTokenRequestArguments(Map arguments) { + final String clientId = (String) arguments.get("clientId"); + final String issuer = (String) arguments.get("issuer"); + final String discoveryUrl = (String) arguments.get("discoveryUrl"); + final String redirectUrl = (String) arguments.get("redirectUrl"); + final String grantType = (String) arguments.get("grantType"); + clientSecret = (String) arguments.get("clientSecret"); + String refreshToken = null; + if (arguments.containsKey("refreshToken")) { + refreshToken = (String) arguments.get("refreshToken"); + } + String authorizationCode = null; + if (arguments.containsKey("authorizationCode")) { + authorizationCode = (String) arguments.get("authorizationCode"); + } + String codeVerifier = null; + if (arguments.containsKey("codeVerifier")) { + codeVerifier = (String) arguments.get("codeVerifier"); + } + String nonce = null; + if (arguments.containsKey("nonce")) { + nonce = (String) arguments.get("nonce"); + } + final ArrayList scopes = (ArrayList) arguments.get("scopes"); + final Map serviceConfigurationParameters = + (Map) arguments.get("serviceConfiguration"); + final Map additionalParameters = + (Map) arguments.get("additionalParameters"); + allowInsecureConnections = (boolean) arguments.get("allowInsecureConnections"); + return new TokenRequestParameters( + clientId, + issuer, + discoveryUrl, + scopes, + redirectUrl, + refreshToken, + authorizationCode, + codeVerifier, + nonce, + grantType, + serviceConfigurationParameters, + additionalParameters); + } + + @SuppressWarnings("unchecked") + private EndSessionRequestParameters processEndSessionRequestArguments( + Map arguments) { + final String idTokenHint = (String) arguments.get("idTokenHint"); + final String postLogoutRedirectUrl = (String) arguments.get("postLogoutRedirectUrl"); + final String state = (String) arguments.get("state"); + final boolean allowInsecureConnections = (boolean) arguments.get("allowInsecureConnections"); + final String issuer = (String) arguments.get("issuer"); + final String discoveryUrl = (String) arguments.get("discoveryUrl"); + final Map serviceConfigurationParameters = + (Map) arguments.get("serviceConfiguration"); + final Map additionalParameters = + (Map) arguments.get("additionalParameters"); + return new EndSessionRequestParameters( + idTokenHint, + postLogoutRedirectUrl, + state, + issuer, + discoveryUrl, + allowInsecureConnections, + serviceConfigurationParameters, + additionalParameters); + } + + private void handleAuthorizeMethodCall( + Map arguments, final boolean exchangeCode) { + final AuthorizationTokenRequestParameters tokenRequestParameters = + processAuthorizationTokenRequestArguments(arguments); + if (tokenRequestParameters.serviceConfigurationParameters != null) { + AuthorizationServiceConfiguration serviceConfiguration = + processServiceConfigurationParameters( + tokenRequestParameters.serviceConfigurationParameters); + performAuthorization( + serviceConfiguration, + tokenRequestParameters.clientId, + tokenRequestParameters.redirectUrl, + tokenRequestParameters.scopes, + tokenRequestParameters.loginHint, + tokenRequestParameters.nonce, + tokenRequestParameters.additionalParameters, + exchangeCode, + tokenRequestParameters.promptValues, + tokenRequestParameters.responseMode); + } else { + AuthorizationServiceConfiguration.RetrieveConfigurationCallback callback = + new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { + @Override + public void onFetchConfigurationCompleted( + @Nullable AuthorizationServiceConfiguration serviceConfiguration, + @Nullable AuthorizationException ex) { + if (ex == null) { + performAuthorization( + serviceConfiguration, + tokenRequestParameters.clientId, + tokenRequestParameters.redirectUrl, + tokenRequestParameters.scopes, + tokenRequestParameters.loginHint, + tokenRequestParameters.nonce, + tokenRequestParameters.additionalParameters, + exchangeCode, + tokenRequestParameters.promptValues, + tokenRequestParameters.responseMode); + } else { + finishWithDiscoveryError(ex); + } } - - authRequestBuilder.setAdditionalParameters(additionalParameters); - } - - AuthorizationService authorizationService = allowInsecureConnections ? insecureAuthorizationService : defaultAuthorizationService; - Intent authIntent = authorizationService.getAuthorizationRequestIntent(authRequestBuilder.build()); - mainActivity.startActivityForResult(authIntent, exchangeCode ? RC_AUTH_EXCHANGE_CODE : RC_AUTH); - } - - private void performTokenRequest(AuthorizationServiceConfiguration serviceConfiguration, TokenRequestParameters tokenRequestParameters) { - TokenRequest.Builder builder = new TokenRequest.Builder(serviceConfiguration, tokenRequestParameters.clientId) - .setRefreshToken(tokenRequestParameters.refreshToken) - .setAuthorizationCode(tokenRequestParameters.authorizationCode) - .setCodeVerifier(tokenRequestParameters.codeVerifier) - .setRedirectUri(Uri.parse(tokenRequestParameters.redirectUrl)); - - if (tokenRequestParameters.nonce != null) { - builder.setNonce(tokenRequestParameters.nonce); - } - if (tokenRequestParameters.grantType != null) { - builder.setGrantType(tokenRequestParameters.grantType); - } - if (tokenRequestParameters.scopes != null) { - builder.setScopes(tokenRequestParameters.scopes); - } - - if (tokenRequestParameters.additionalParameters != null && !tokenRequestParameters.additionalParameters.isEmpty()) { - builder.setAdditionalParameters(tokenRequestParameters.additionalParameters); - } - - AuthorizationService.TokenResponseCallback tokenResponseCallback = new AuthorizationService.TokenResponseCallback() { + }; + if (tokenRequestParameters.discoveryUrl != null) { + AuthorizationServiceConfiguration.fetchFromUrl( + Uri.parse(tokenRequestParameters.discoveryUrl), + callback, + allowInsecureConnections + ? InsecureConnectionBuilder.INSTANCE + : DefaultConnectionBuilder.INSTANCE); + } else { + AuthorizationServiceConfiguration.fetchFromIssuer( + Uri.parse(tokenRequestParameters.issuer), + callback, + allowInsecureConnections + ? InsecureConnectionBuilder.INSTANCE + : DefaultConnectionBuilder.INSTANCE); + } + } + } + + private AuthorizationServiceConfiguration processServiceConfigurationParameters( + Map serviceConfigurationArguments) { + final String endSessionEndpoint = serviceConfigurationArguments.get("endSessionEndpoint"); + return new AuthorizationServiceConfiguration( + Uri.parse(serviceConfigurationArguments.get("authorizationEndpoint")), + Uri.parse(serviceConfigurationArguments.get("tokenEndpoint")), + null, + endSessionEndpoint == null ? null : Uri.parse(endSessionEndpoint)); + } + + private void handleTokenMethodCall(Map arguments) { + final TokenRequestParameters tokenRequestParameters = processTokenRequestArguments(arguments); + if (tokenRequestParameters.serviceConfigurationParameters != null) { + AuthorizationServiceConfiguration serviceConfiguration = + processServiceConfigurationParameters( + tokenRequestParameters.serviceConfigurationParameters); + performTokenRequest(serviceConfiguration, tokenRequestParameters); + } else { + AuthorizationServiceConfiguration.RetrieveConfigurationCallback callback = + new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { @Override - public void onTokenRequestCompleted( - TokenResponse resp, AuthorizationException ex) { - if (resp != null) { - Map responseMap = tokenResponseToMap(resp, null); - finishWithSuccess(responseMap); - } else { - finishWithTokenError(ex); - } + public void onFetchConfigurationCompleted( + @Nullable AuthorizationServiceConfiguration serviceConfiguration, + @Nullable AuthorizationException ex) { + if (ex == null) { + performTokenRequest(serviceConfiguration, tokenRequestParameters); + } else { + finishWithDiscoveryError(ex); + } } - }; - - TokenRequest tokenRequest = builder.build(); - AuthorizationService authorizationService = allowInsecureConnections ? insecureAuthorizationService : defaultAuthorizationService; - if (clientSecret == null) { - authorizationService.performTokenRequest(tokenRequest, tokenResponseCallback); - } else { - authorizationService.performTokenRequest(tokenRequest, new ClientSecretBasic(clientSecret), tokenResponseCallback); - } + }; + if (tokenRequestParameters.discoveryUrl != null) { + AuthorizationServiceConfiguration.fetchFromUrl( + Uri.parse(tokenRequestParameters.discoveryUrl), + callback, + allowInsecureConnections + ? InsecureConnectionBuilder.INSTANCE + : DefaultConnectionBuilder.INSTANCE); + } else { + AuthorizationServiceConfiguration.fetchFromIssuer( + Uri.parse(tokenRequestParameters.issuer), + callback, + allowInsecureConnections + ? InsecureConnectionBuilder.INSTANCE + : DefaultConnectionBuilder.INSTANCE); + } + } + } + + private void performAuthorization( + AuthorizationServiceConfiguration serviceConfiguration, + String clientId, + String redirectUrl, + ArrayList scopes, + String loginHint, + String nonce, + Map additionalParameters, + boolean exchangeCode, + ArrayList promptValues, + String responseMode) { + AuthorizationRequest.Builder authRequestBuilder = + new AuthorizationRequest.Builder( + serviceConfiguration, clientId, ResponseTypeValues.CODE, Uri.parse(redirectUrl)); + + if (scopes != null && !scopes.isEmpty()) { + authRequestBuilder.setScopes(scopes); + } + + if (loginHint != null) { + authRequestBuilder.setLoginHint(loginHint); + } + + if (promptValues != null && !promptValues.isEmpty()) { + authRequestBuilder.setPromptValues(promptValues); + } + + if (responseMode != null) { + authRequestBuilder.setResponseMode(responseMode); + } + + if (nonce != null) { + authRequestBuilder.setNonce(nonce); + } + + if (additionalParameters != null && !additionalParameters.isEmpty()) { + + if (additionalParameters.containsKey("ui_locales")) { + authRequestBuilder.setUiLocales(additionalParameters.get("ui_locales")); + additionalParameters.remove("ui_locales"); + } + + if (additionalParameters.containsKey("claims")) { + try { + final JSONObject claimsAsJson = new JSONObject(additionalParameters.get("claims")); + authRequestBuilder.setClaims(claimsAsJson); + additionalParameters.remove("claims"); + } catch (JSONException ex) { + finishWithError( + INVALID_CLAIMS_ERROR_CODE, ex.getLocalizedMessage(), getCauseFromException(ex)); + return; + } + } + + authRequestBuilder.setAdditionalParameters(additionalParameters); + } + + AuthorizationService authorizationService = + allowInsecureConnections ? insecureAuthorizationService : defaultAuthorizationService; + Intent authIntent = + authorizationService.getAuthorizationRequestIntent(authRequestBuilder.build()); + mainActivity.startActivityForResult(authIntent, exchangeCode ? RC_AUTH_EXCHANGE_CODE : RC_AUTH); + } + + private void performTokenRequest( + AuthorizationServiceConfiguration serviceConfiguration, + TokenRequestParameters tokenRequestParameters) { + TokenRequest.Builder builder = + new TokenRequest.Builder(serviceConfiguration, tokenRequestParameters.clientId) + .setRefreshToken(tokenRequestParameters.refreshToken) + .setAuthorizationCode(tokenRequestParameters.authorizationCode) + .setCodeVerifier(tokenRequestParameters.codeVerifier) + .setRedirectUri(Uri.parse(tokenRequestParameters.redirectUrl)); + + if (tokenRequestParameters.nonce != null) { + builder.setNonce(tokenRequestParameters.nonce); + } + if (tokenRequestParameters.grantType != null) { + builder.setGrantType(tokenRequestParameters.grantType); + } + if (tokenRequestParameters.scopes != null) { + builder.setScopes(tokenRequestParameters.scopes); + } + + if (tokenRequestParameters.additionalParameters != null + && !tokenRequestParameters.additionalParameters.isEmpty()) { + builder.setAdditionalParameters(tokenRequestParameters.additionalParameters); } - private void handleEndSessionMethodCall(Map arguments) { - final EndSessionRequestParameters endSessionRequestParameters = processEndSessionRequestArguments(arguments); - if (endSessionRequestParameters.serviceConfigurationParameters != null) { - AuthorizationServiceConfiguration serviceConfiguration = processServiceConfigurationParameters(endSessionRequestParameters.serviceConfigurationParameters); - performEndSessionRequest(serviceConfiguration, endSessionRequestParameters); - } else { - AuthorizationServiceConfiguration.RetrieveConfigurationCallback callback = new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { - @Override - public void onFetchConfigurationCompleted(@Nullable AuthorizationServiceConfiguration serviceConfiguration, @Nullable AuthorizationException ex) { - if (ex == null) { - performEndSessionRequest(serviceConfiguration, endSessionRequestParameters); - } else { - finishWithDiscoveryError(ex); - } - } - }; - - if (endSessionRequestParameters.discoveryUrl != null) { - AuthorizationServiceConfiguration.fetchFromUrl(Uri.parse(endSessionRequestParameters.discoveryUrl), callback, allowInsecureConnections ? InsecureConnectionBuilder.INSTANCE : DefaultConnectionBuilder.INSTANCE); + AuthorizationService.TokenResponseCallback tokenResponseCallback = + new AuthorizationService.TokenResponseCallback() { + @Override + public void onTokenRequestCompleted(TokenResponse resp, AuthorizationException ex) { + if (resp != null) { + Map responseMap = tokenResponseToMap(resp, null); + finishWithSuccess(responseMap); } else { - AuthorizationServiceConfiguration.fetchFromIssuer(Uri.parse(endSessionRequestParameters.issuer), callback, allowInsecureConnections ? InsecureConnectionBuilder.INSTANCE : DefaultConnectionBuilder.INSTANCE); + finishWithTokenError(ex); } - } - } - - private void performEndSessionRequest(AuthorizationServiceConfiguration serviceConfiguration, final EndSessionRequestParameters endSessionRequestParameters) { - EndSessionRequest.Builder endSessionRequestBuilder = new EndSessionRequest.Builder(serviceConfiguration); - if (endSessionRequestParameters.idTokenHint != null) { - endSessionRequestBuilder.setIdTokenHint(endSessionRequestParameters.idTokenHint); - } - - if (endSessionRequestParameters.postLogoutRedirectUrl != null) { - endSessionRequestBuilder.setPostLogoutRedirectUri(Uri.parse(endSessionRequestParameters.postLogoutRedirectUrl)); - } - - if (endSessionRequestParameters.state != null) { - endSessionRequestBuilder.setState(endSessionRequestParameters.state); - } - - if (endSessionRequestParameters.additionalParameters != null) { - endSessionRequestBuilder.setAdditionalParameters(endSessionRequestParameters.additionalParameters); - } - - final EndSessionRequest endSessionRequest = endSessionRequestBuilder.build(); - AuthorizationService authorizationService = allowInsecureConnections ? insecureAuthorizationService : defaultAuthorizationService; - Intent endSessionIntent = authorizationService.getEndSessionRequestIntent(endSessionRequest); - mainActivity.startActivityForResult(endSessionIntent, RC_END_SESSION); - } - - private void finishWithTokenError(AuthorizationException ex) { - finishWithError(TOKEN_ERROR_CODE, String.format(TOKEN_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), getCauseFromException(ex)); - } - - - private void finishWithSuccess(Object data) { - if (pendingOperation != null) { - pendingOperation.result.success(data); - pendingOperation = null; - } - } - - private void finishWithError(String errorCode, String errorMessage, String errorDetails) { - if (pendingOperation != null) { - pendingOperation.result.error(errorCode, errorMessage, errorDetails); - pendingOperation = null; - } - } - - private void finishWithDiscoveryError(AuthorizationException ex) { - finishWithError(DISCOVERY_ERROR_CODE, String.format(DISCOVERY_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), getCauseFromException(ex)); - } - - private void finishWithEndSessionError(AuthorizationException ex) { - finishWithError(END_SESSION_ERROR_CODE, String.format(END_SESSION_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), getCauseFromException(ex)); - } - - private String getCauseFromException(Exception ex) { - final Throwable cause = ex.getCause(); - return cause != null ? cause.getMessage() : null; - } - + } + }; - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent intent) { - if (pendingOperation == null) { - return false; - } - if (requestCode == RC_AUTH_EXCHANGE_CODE || requestCode == RC_AUTH) { - if (intent == null) { - finishWithError(NULL_INTENT_ERROR_CODE, NULL_INTENT_ERROR_FORMAT, null); - } else { - final AuthorizationResponse authResponse = AuthorizationResponse.fromIntent(intent); - AuthorizationException ex = AuthorizationException.fromIntent(intent); - processAuthorizationData(authResponse, ex, requestCode == RC_AUTH_EXCHANGE_CODE); - } - return true; - } - if (requestCode == RC_END_SESSION) { - if (intent == null) { - finishWithError(NULL_INTENT_ERROR_CODE, NULL_INTENT_ERROR_FORMAT, null); - } else { - final EndSessionResponse endSessionResponse = EndSessionResponse.fromIntent(intent); - AuthorizationException ex = AuthorizationException.fromIntent(intent); - if (ex != null) { - finishWithEndSessionError(ex); - } else { - Map responseMap = new HashMap<>(); - responseMap.put("state", endSessionResponse.state); - finishWithSuccess(responseMap); - } + TokenRequest tokenRequest = builder.build(); + AuthorizationService authorizationService = + allowInsecureConnections ? insecureAuthorizationService : defaultAuthorizationService; + if (clientSecret == null) { + authorizationService.performTokenRequest(tokenRequest, tokenResponseCallback); + } else { + authorizationService.performTokenRequest( + tokenRequest, new ClientSecretBasic(clientSecret), tokenResponseCallback); + } + } + + private void handleEndSessionMethodCall(Map arguments) { + final EndSessionRequestParameters endSessionRequestParameters = + processEndSessionRequestArguments(arguments); + if (endSessionRequestParameters.serviceConfigurationParameters != null) { + AuthorizationServiceConfiguration serviceConfiguration = + processServiceConfigurationParameters( + endSessionRequestParameters.serviceConfigurationParameters); + performEndSessionRequest(serviceConfiguration, endSessionRequestParameters); + } else { + AuthorizationServiceConfiguration.RetrieveConfigurationCallback callback = + new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { + @Override + public void onFetchConfigurationCompleted( + @Nullable AuthorizationServiceConfiguration serviceConfiguration, + @Nullable AuthorizationException ex) { + if (ex == null) { + performEndSessionRequest(serviceConfiguration, endSessionRequestParameters); + } else { + finishWithDiscoveryError(ex); + } } - return true; + }; + + if (endSessionRequestParameters.discoveryUrl != null) { + AuthorizationServiceConfiguration.fetchFromUrl( + Uri.parse(endSessionRequestParameters.discoveryUrl), + callback, + allowInsecureConnections + ? InsecureConnectionBuilder.INSTANCE + : DefaultConnectionBuilder.INSTANCE); + } else { + AuthorizationServiceConfiguration.fetchFromIssuer( + Uri.parse(endSessionRequestParameters.issuer), + callback, + allowInsecureConnections + ? InsecureConnectionBuilder.INSTANCE + : DefaultConnectionBuilder.INSTANCE); + } + } + } + + private void performEndSessionRequest( + AuthorizationServiceConfiguration serviceConfiguration, + final EndSessionRequestParameters endSessionRequestParameters) { + EndSessionRequest.Builder endSessionRequestBuilder = + new EndSessionRequest.Builder(serviceConfiguration); + if (endSessionRequestParameters.idTokenHint != null) { + endSessionRequestBuilder.setIdTokenHint(endSessionRequestParameters.idTokenHint); + } + + if (endSessionRequestParameters.postLogoutRedirectUrl != null) { + endSessionRequestBuilder.setPostLogoutRedirectUri( + Uri.parse(endSessionRequestParameters.postLogoutRedirectUrl)); + } + + if (endSessionRequestParameters.state != null) { + endSessionRequestBuilder.setState(endSessionRequestParameters.state); + } + + if (endSessionRequestParameters.additionalParameters != null) { + endSessionRequestBuilder.setAdditionalParameters( + endSessionRequestParameters.additionalParameters); + } + + final EndSessionRequest endSessionRequest = endSessionRequestBuilder.build(); + AuthorizationService authorizationService = + allowInsecureConnections ? insecureAuthorizationService : defaultAuthorizationService; + Intent endSessionIntent = authorizationService.getEndSessionRequestIntent(endSessionRequest); + mainActivity.startActivityForResult(endSessionIntent, RC_END_SESSION); + } + + private void finishWithTokenError(AuthorizationException ex) { + finishWithError( + TOKEN_ERROR_CODE, + String.format(TOKEN_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), + getCauseFromException(ex)); + } + + private void finishWithSuccess(Object data) { + if (pendingOperation != null) { + pendingOperation.result.success(data); + pendingOperation = null; + } + } + + private void finishWithError(String errorCode, String errorMessage, String errorDetails) { + if (pendingOperation != null) { + pendingOperation.result.error(errorCode, errorMessage, errorDetails); + pendingOperation = null; + } + } + + private void finishWithDiscoveryError(AuthorizationException ex) { + finishWithError( + DISCOVERY_ERROR_CODE, + String.format(DISCOVERY_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), + getCauseFromException(ex)); + } + + private void finishWithEndSessionError(AuthorizationException ex) { + finishWithError( + END_SESSION_ERROR_CODE, + String.format(END_SESSION_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), + getCauseFromException(ex)); + } + + private String getCauseFromException(Exception ex) { + final Throwable cause = ex.getCause(); + return cause != null ? cause.getMessage() : null; + } + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent intent) { + if (pendingOperation == null) { + return false; + } + if (requestCode == RC_AUTH_EXCHANGE_CODE || requestCode == RC_AUTH) { + if (intent == null) { + finishWithError(NULL_INTENT_ERROR_CODE, NULL_INTENT_ERROR_FORMAT, null); + } else { + final AuthorizationResponse authResponse = AuthorizationResponse.fromIntent(intent); + AuthorizationException ex = AuthorizationException.fromIntent(intent); + processAuthorizationData(authResponse, ex, requestCode == RC_AUTH_EXCHANGE_CODE); + } + return true; + } + if (requestCode == RC_END_SESSION) { + if (intent == null) { + finishWithError(NULL_INTENT_ERROR_CODE, NULL_INTENT_ERROR_FORMAT, null); + } else { + final EndSessionResponse endSessionResponse = EndSessionResponse.fromIntent(intent); + AuthorizationException ex = AuthorizationException.fromIntent(intent); + if (ex != null) { + finishWithEndSessionError(ex); + } else { + Map responseMap = new HashMap<>(); + responseMap.put("state", endSessionResponse.state); + finishWithSuccess(responseMap); + } + } + return true; + } + return false; + } + + private void processAuthorizationData( + final AuthorizationResponse authResponse, + AuthorizationException authException, + boolean exchangeCode) { + if (authException == null) { + if (exchangeCode) { + AppAuthConfiguration.Builder authConfigBuilder = new AppAuthConfiguration.Builder(); + if (allowInsecureConnections) { + authConfigBuilder.setConnectionBuilder(InsecureConnectionBuilder.INSTANCE); + authConfigBuilder.setSkipIssuerHttpsCheck(true); } - return false; - } - private void processAuthorizationData(final AuthorizationResponse authResponse, AuthorizationException authException, boolean exchangeCode) { - if (authException == null) { - if (exchangeCode) { - AppAuthConfiguration.Builder authConfigBuilder = new AppAuthConfiguration.Builder(); - if (allowInsecureConnections) { - authConfigBuilder.setConnectionBuilder(InsecureConnectionBuilder.INSTANCE); - authConfigBuilder.setSkipIssuerHttpsCheck(true); - } - - AuthorizationService authService = new AuthorizationService(applicationContext, authConfigBuilder.build()); - AuthorizationService.TokenResponseCallback tokenResponseCallback = new AuthorizationService.TokenResponseCallback() { - @Override - public void onTokenRequestCompleted( - TokenResponse resp, AuthorizationException ex) { - if (resp != null) { - finishWithSuccess(tokenResponseToMap(resp, authResponse)); - } else { - finishWithError(AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE, String.format(AUTHORIZE_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), getCauseFromException(ex)); - } - } - }; - if (clientSecret == null) { - authService.performTokenRequest(authResponse.createTokenExchangeRequest(), tokenResponseCallback); + AuthorizationService authService = + new AuthorizationService(applicationContext, authConfigBuilder.build()); + AuthorizationService.TokenResponseCallback tokenResponseCallback = + new AuthorizationService.TokenResponseCallback() { + @Override + public void onTokenRequestCompleted(TokenResponse resp, AuthorizationException ex) { + if (resp != null) { + finishWithSuccess(tokenResponseToMap(resp, authResponse)); } else { - authService.performTokenRequest(authResponse.createTokenExchangeRequest(), new ClientSecretBasic(clientSecret), tokenResponseCallback); + finishWithError( + AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE, + String.format(AUTHORIZE_ERROR_MESSAGE_FORMAT, ex.error, ex.errorDescription), + getCauseFromException(ex)); } - } else { - finishWithSuccess(authorizationResponseToMap(authResponse)); - } + } + }; + if (clientSecret == null) { + authService.performTokenRequest( + authResponse.createTokenExchangeRequest(), tokenResponseCallback); } else { - finishWithError(exchangeCode ? AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE : AUTHORIZE_ERROR_CODE, String.format(AUTHORIZE_ERROR_MESSAGE_FORMAT, authException.error, authException.errorDescription), getCauseFromException(authException)); - } - } - - private Map tokenResponseToMap(TokenResponse tokenResponse, AuthorizationResponse authResponse) { - Map responseMap = new HashMap<>(); - responseMap.put("accessToken", tokenResponse.accessToken); - responseMap.put("accessTokenExpirationTime", tokenResponse.accessTokenExpirationTime != null ? tokenResponse.accessTokenExpirationTime.doubleValue() : null); - responseMap.put("refreshToken", tokenResponse.refreshToken); - responseMap.put("idToken", tokenResponse.idToken); - responseMap.put("tokenType", tokenResponse.tokenType); - responseMap.put("scopes", tokenResponse.scope != null ? Arrays.asList(tokenResponse.scope.split(" ")) : null); - if (authResponse != null) { - responseMap.put("authorizationAdditionalParameters", authResponse.additionalParameters); - } - responseMap.put("tokenAdditionalParameters", tokenResponse.additionalParameters); - - return responseMap; - } - - private Map authorizationResponseToMap(AuthorizationResponse authResponse) { - Map responseMap = new HashMap<>(); - responseMap.put("codeVerifier", authResponse.request.codeVerifier); - responseMap.put("nonce", authResponse.request.nonce); - responseMap.put("authorizationCode", authResponse.authorizationCode); - responseMap.put("authorizationAdditionalParameters", authResponse.additionalParameters); - return responseMap; - } - - private class PendingOperation { - final String method; - final Result result; - - PendingOperation(String method, Result result) { - this.method = method; - this.result = result; - } - } - - private class TokenRequestParameters { - final String clientId; - final String issuer; - final String discoveryUrl; - final ArrayList scopes; - final String redirectUrl; - final String refreshToken; - final String grantType; - final String codeVerifier; - final String nonce; - final String authorizationCode; - final Map serviceConfigurationParameters; - final Map additionalParameters; - - private TokenRequestParameters(String clientId, String issuer, String discoveryUrl, ArrayList scopes, String redirectUrl, String refreshToken, String authorizationCode, String codeVerifier, String nonce, String grantType, Map serviceConfigurationParameters, Map additionalParameters) { - this.clientId = clientId; - this.issuer = issuer; - this.discoveryUrl = discoveryUrl; - this.scopes = scopes; - this.redirectUrl = redirectUrl; - this.refreshToken = refreshToken; - this.authorizationCode = authorizationCode; - this.codeVerifier = codeVerifier; - this.nonce = nonce; - this.grantType = grantType; - this.serviceConfigurationParameters = serviceConfigurationParameters; - this.additionalParameters = additionalParameters; - } - } - - private class EndSessionRequestParameters { - final String idTokenHint; - final String postLogoutRedirectUrl; - final String state; - final String issuer; - final String discoveryUrl; - final boolean allowInsecureConnections; - final Map serviceConfigurationParameters; - final Map additionalParameters; - - private EndSessionRequestParameters(String idTokenHint, String postLogoutRedirectUrl, String state, String issuer, String discoveryUrl, boolean allowInsecureConnections, Map serviceConfigurationParameters, Map additionalParameters) { - this.idTokenHint = idTokenHint; - this.postLogoutRedirectUrl = postLogoutRedirectUrl; - this.state = state; - this.issuer = issuer; - this.discoveryUrl = discoveryUrl; - this.allowInsecureConnections = allowInsecureConnections; - this.serviceConfigurationParameters = serviceConfigurationParameters; - this.additionalParameters = additionalParameters; - } - } - - private class AuthorizationTokenRequestParameters extends TokenRequestParameters { - final String loginHint; - final ArrayList promptValues; - final String responseMode; - - private AuthorizationTokenRequestParameters(String clientId, String issuer, String discoveryUrl, ArrayList scopes, String redirectUrl, Map serviceConfigurationParameters, Map additionalParameters, String loginHint, String nonce, ArrayList promptValues, String responseMode) { - super(clientId, issuer, discoveryUrl, scopes, redirectUrl, null, null, null, nonce, null, serviceConfigurationParameters, additionalParameters); - this.loginHint = loginHint; - this.promptValues = promptValues; - this.responseMode = responseMode; - } - } - + authService.performTokenRequest( + authResponse.createTokenExchangeRequest(), + new ClientSecretBasic(clientSecret), + tokenResponseCallback); + } + } else { + finishWithSuccess(authorizationResponseToMap(authResponse)); + } + } else { + finishWithError( + exchangeCode ? AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE : AUTHORIZE_ERROR_CODE, + String.format( + AUTHORIZE_ERROR_MESSAGE_FORMAT, authException.error, authException.errorDescription), + getCauseFromException(authException)); + } + } + + private Map tokenResponseToMap( + TokenResponse tokenResponse, AuthorizationResponse authResponse) { + Map responseMap = new HashMap<>(); + responseMap.put("accessToken", tokenResponse.accessToken); + responseMap.put( + "accessTokenExpirationTime", + tokenResponse.accessTokenExpirationTime != null + ? tokenResponse.accessTokenExpirationTime.doubleValue() + : null); + responseMap.put("refreshToken", tokenResponse.refreshToken); + responseMap.put("idToken", tokenResponse.idToken); + responseMap.put("tokenType", tokenResponse.tokenType); + responseMap.put( + "scopes", + tokenResponse.scope != null ? Arrays.asList(tokenResponse.scope.split(" ")) : null); + if (authResponse != null) { + responseMap.put("authorizationAdditionalParameters", authResponse.additionalParameters); + } + responseMap.put("tokenAdditionalParameters", tokenResponse.additionalParameters); + + return responseMap; + } + + private Map authorizationResponseToMap(AuthorizationResponse authResponse) { + Map responseMap = new HashMap<>(); + responseMap.put("codeVerifier", authResponse.request.codeVerifier); + responseMap.put("nonce", authResponse.request.nonce); + responseMap.put("authorizationCode", authResponse.authorizationCode); + responseMap.put("authorizationAdditionalParameters", authResponse.additionalParameters); + return responseMap; + } + + private class PendingOperation { + final String method; + final Result result; + + PendingOperation(String method, Result result) { + this.method = method; + this.result = result; + } + } + + private class TokenRequestParameters { + final String clientId; + final String issuer; + final String discoveryUrl; + final ArrayList scopes; + final String redirectUrl; + final String refreshToken; + final String grantType; + final String codeVerifier; + final String nonce; + final String authorizationCode; + final Map serviceConfigurationParameters; + final Map additionalParameters; + + private TokenRequestParameters( + String clientId, + String issuer, + String discoveryUrl, + ArrayList scopes, + String redirectUrl, + String refreshToken, + String authorizationCode, + String codeVerifier, + String nonce, + String grantType, + Map serviceConfigurationParameters, + Map additionalParameters) { + this.clientId = clientId; + this.issuer = issuer; + this.discoveryUrl = discoveryUrl; + this.scopes = scopes; + this.redirectUrl = redirectUrl; + this.refreshToken = refreshToken; + this.authorizationCode = authorizationCode; + this.codeVerifier = codeVerifier; + this.nonce = nonce; + this.grantType = grantType; + this.serviceConfigurationParameters = serviceConfigurationParameters; + this.additionalParameters = additionalParameters; + } + } + + private class EndSessionRequestParameters { + final String idTokenHint; + final String postLogoutRedirectUrl; + final String state; + final String issuer; + final String discoveryUrl; + final boolean allowInsecureConnections; + final Map serviceConfigurationParameters; + final Map additionalParameters; + + private EndSessionRequestParameters( + String idTokenHint, + String postLogoutRedirectUrl, + String state, + String issuer, + String discoveryUrl, + boolean allowInsecureConnections, + Map serviceConfigurationParameters, + Map additionalParameters) { + this.idTokenHint = idTokenHint; + this.postLogoutRedirectUrl = postLogoutRedirectUrl; + this.state = state; + this.issuer = issuer; + this.discoveryUrl = discoveryUrl; + this.allowInsecureConnections = allowInsecureConnections; + this.serviceConfigurationParameters = serviceConfigurationParameters; + this.additionalParameters = additionalParameters; + } + } + + private class AuthorizationTokenRequestParameters extends TokenRequestParameters { + final String loginHint; + final ArrayList promptValues; + final String responseMode; + + private AuthorizationTokenRequestParameters( + String clientId, + String issuer, + String discoveryUrl, + ArrayList scopes, + String redirectUrl, + Map serviceConfigurationParameters, + Map additionalParameters, + String loginHint, + String nonce, + ArrayList promptValues, + String responseMode) { + super( + clientId, + issuer, + discoveryUrl, + scopes, + redirectUrl, + null, + null, + null, + nonce, + null, + serviceConfigurationParameters, + additionalParameters); + this.loginHint = loginHint; + this.promptValues = promptValues; + this.responseMode = responseMode; + } + } } diff --git a/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/InsecureConnectionBuilder.java b/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/InsecureConnectionBuilder.java index b46d730c..a52cc368 100644 --- a/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/InsecureConnectionBuilder.java +++ b/flutter_appauth/android/src/main/java/io/crossingthestreams/flutterappauth/InsecureConnectionBuilder.java @@ -12,13 +12,13 @@ public class InsecureConnectionBuilder implements ConnectionBuilder { - public static final InsecureConnectionBuilder INSTANCE = new InsecureConnectionBuilder(); + public static final InsecureConnectionBuilder INSTANCE = new InsecureConnectionBuilder(); - private InsecureConnectionBuilder() { } + private InsecureConnectionBuilder() {} - @NonNull - @Override - public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException { - return (HttpURLConnection) new URL(uri.toString()).openConnection(); - } + @NonNull + @Override + public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException { + return (HttpURLConnection) new URL(uri.toString()).openConnection(); + } } diff --git a/flutter_appauth/example/android/app/src/main/java/io/crossingthestreams/flutterappauthexample/MainActivity.java b/flutter_appauth/example/android/app/src/main/java/io/crossingthestreams/flutterappauthexample/MainActivity.java index 78505a7a..4712c540 100644 --- a/flutter_appauth/example/android/app/src/main/java/io/crossingthestreams/flutterappauthexample/MainActivity.java +++ b/flutter_appauth/example/android/app/src/main/java/io/crossingthestreams/flutterappauthexample/MainActivity.java @@ -2,5 +2,4 @@ import io.flutter.embedding.android.FlutterActivity; -public class MainActivity extends FlutterActivity { -} +public class MainActivity extends FlutterActivity {} diff --git a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h index d41a9579..0469bd6c 100644 --- a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h +++ b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.h @@ -1,7 +1,7 @@ +#import "FlutterAppAuth.h" +#import "OIDExternalUserAgentIOSNoSSO.h" #import #import -#import "OIDExternalUserAgentIOSNoSSO.h" -#import "FlutterAppAuth.h" NS_ASSUME_NONNULL_BEGIN diff --git a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m index 18092a71..13af67a1 100644 --- a/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m +++ b/flutter_appauth/ios/Classes/AppAuthIOSAuthorization.m @@ -2,92 +2,186 @@ @implementation AppAuthIOSAuthorization -- (id) performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce{ +- (id) + performAuthorization:(OIDServiceConfiguration *)serviceConfiguration + clientId:(NSString *)clientId + clientSecret:(NSString *)clientSecret + scopes:(NSArray *)scopes + redirectUrl:(NSString *)redirectUrl + additionalParameters:(NSDictionary *)additionalParameters + preferEphemeralSession:(BOOL)preferEphemeralSession + result:(FlutterResult)result + exchangeCode:(BOOL)exchangeCode + nonce:(NSString *)nonce { NSString *codeVerifier = [OIDAuthorizationRequest generateCodeVerifier]; - NSString *codeChallenge = [OIDAuthorizationRequest codeChallengeS256ForVerifier:codeVerifier]; + NSString *codeChallenge = + [OIDAuthorizationRequest codeChallengeS256ForVerifier:codeVerifier]; - OIDAuthorizationRequest *request = - [[OIDAuthorizationRequest alloc] initWithConfiguration:serviceConfiguration - clientId:clientId - clientSecret:clientSecret - scope:[OIDScopeUtilities scopesWithArray:scopes] - redirectURL:[NSURL URLWithString:redirectUrl] - responseType:OIDResponseTypeCode - state:[OIDAuthorizationRequest generateState] - nonce: nonce != nil ? nonce : [OIDAuthorizationRequest generateState] - codeVerifier:codeVerifier - codeChallenge:codeChallenge - codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256 - additionalParameters:additionalParameters]; + OIDAuthorizationRequest *request = [[OIDAuthorizationRequest alloc] + initWithConfiguration:serviceConfiguration + clientId:clientId + clientSecret:clientSecret + scope:[OIDScopeUtilities scopesWithArray:scopes] + redirectURL:[NSURL URLWithString:redirectUrl] + responseType:OIDResponseTypeCode + state:[OIDAuthorizationRequest generateState] + nonce:nonce != nil + ? nonce + : [OIDAuthorizationRequest generateState] + codeVerifier:codeVerifier + codeChallenge:codeChallenge + codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256 + additionalParameters:additionalParameters]; UIViewController *rootViewController = [self rootViewController]; - if(exchangeCode) { - id externalUserAgent = [self userAgentWithViewController:rootViewController useEphemeralSession:preferEphemeralSession]; - return [OIDAuthState authStateByPresentingAuthorizationRequest:request externalUserAgent:externalUserAgent callback:^(OIDAuthState *_Nullable authState, - NSError *_Nullable error) { - if(authState) { - result([FlutterAppAuth processResponses:authState.lastTokenResponse authResponse:authState.lastAuthorizationResponse]); - - } else { - [FlutterAppAuth finishWithError:AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE message:[FlutterAppAuth formatMessageWithError:AUTHORIZE_ERROR_MESSAGE_FORMAT error:error] result:result]; - } - }]; + if (exchangeCode) { + id externalUserAgent = + [self userAgentWithViewController:rootViewController + useEphemeralSession:preferEphemeralSession]; + return [OIDAuthState + authStateByPresentingAuthorizationRequest:request + externalUserAgent:externalUserAgent + callback:^( + OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + if (authState) { + result([FlutterAppAuth + processResponses: + authState.lastTokenResponse + authResponse: + authState + .lastAuthorizationResponse]); + + } else { + [FlutterAppAuth + finishWithError: + AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE + message: + [FlutterAppAuth + formatMessageWithError: + AUTHORIZE_ERROR_MESSAGE_FORMAT + error: + error] + result:result]; + } + }]; } else { - id externalUserAgent = [self userAgentWithViewController:rootViewController useEphemeralSession:preferEphemeralSession]; - return [OIDAuthorizationService presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) { - if(authorizationResponse) { - NSMutableDictionary *processedResponse = [[NSMutableDictionary alloc] init]; - [processedResponse setObject:authorizationResponse.additionalParameters forKey:@"authorizationAdditionalParameters"]; - [processedResponse setObject:authorizationResponse.authorizationCode forKey:@"authorizationCode"]; - [processedResponse setObject:authorizationResponse.request.codeVerifier forKey:@"codeVerifier"]; - [processedResponse setObject:authorizationResponse.request.nonce forKey:@"nonce"]; - result(processedResponse); - } else { - [FlutterAppAuth finishWithError:AUTHORIZE_ERROR_CODE message:[FlutterAppAuth formatMessageWithError:AUTHORIZE_ERROR_MESSAGE_FORMAT error:error] result:result]; - } - }]; + id externalUserAgent = + [self userAgentWithViewController:rootViewController + useEphemeralSession:preferEphemeralSession]; + return [OIDAuthorizationService + presentAuthorizationRequest:request + externalUserAgent:externalUserAgent + callback:^(OIDAuthorizationResponse + *_Nullable authorizationResponse, + NSError *_Nullable error) { + if (authorizationResponse) { + NSMutableDictionary *processedResponse = + [[NSMutableDictionary alloc] init]; + [processedResponse + setObject:authorizationResponse + .additionalParameters + forKey: + @"authorizationAdditionalParameters"]; + [processedResponse + setObject:authorizationResponse + .authorizationCode + forKey:@"authorizationCode"]; + [processedResponse + setObject:authorizationResponse.request + .codeVerifier + forKey:@"codeVerifier"]; + [processedResponse + setObject:authorizationResponse.request.nonce + forKey:@"nonce"]; + result(processedResponse); + } else { + [FlutterAppAuth + finishWithError:AUTHORIZE_ERROR_CODE + message: + [FlutterAppAuth + formatMessageWithError: + AUTHORIZE_ERROR_MESSAGE_FORMAT + error:error] + result:result]; + } + }]; } } -- (id)performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration requestParameters:(EndSessionRequestParameters *)requestParameters result:(FlutterResult)result { - NSURL *postLogoutRedirectURL = requestParameters.postLogoutRedirectUrl ? [NSURL URLWithString:requestParameters.postLogoutRedirectUrl] : nil; - - OIDEndSessionRequest *endSessionRequest = requestParameters.state ? [[OIDEndSessionRequest alloc] initWithConfiguration:serviceConfiguration idTokenHint:requestParameters.idTokenHint postLogoutRedirectURL:postLogoutRedirectURL - state:requestParameters.state additionalParameters:requestParameters.additionalParameters] :[[OIDEndSessionRequest alloc] initWithConfiguration:serviceConfiguration idTokenHint:requestParameters.idTokenHint postLogoutRedirectURL:postLogoutRedirectURL - additionalParameters:requestParameters.additionalParameters]; +- (id) + performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration + requestParameters:(EndSessionRequestParameters *)requestParameters + result:(FlutterResult)result { + NSURL *postLogoutRedirectURL = + requestParameters.postLogoutRedirectUrl + ? [NSURL URLWithString:requestParameters.postLogoutRedirectUrl] + : nil; + + OIDEndSessionRequest *endSessionRequest = + requestParameters.state + ? [[OIDEndSessionRequest alloc] + initWithConfiguration:serviceConfiguration + idTokenHint:requestParameters.idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:requestParameters.state + additionalParameters:requestParameters.additionalParameters] + : [[OIDEndSessionRequest alloc] + initWithConfiguration:serviceConfiguration + idTokenHint:requestParameters.idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + additionalParameters:requestParameters.additionalParameters]; UIViewController *rootViewController = [self rootViewController]; - id externalUserAgent = [self userAgentWithViewController:rootViewController useEphemeralSession:requestParameters.preferEphemeralSession]; + id externalUserAgent = [self + userAgentWithViewController:rootViewController + useEphemeralSession:requestParameters.preferEphemeralSession]; - - return [OIDAuthorizationService presentEndSessionRequest:endSessionRequest externalUserAgent:externalUserAgent callback:^(OIDEndSessionResponse * _Nullable endSessionResponse, NSError * _Nullable error) { - if(!endSessionResponse) { - NSString *message = [NSString stringWithFormat:END_SESSION_ERROR_MESSAGE_FORMAT, [error localizedDescription]]; - [FlutterAppAuth finishWithError:END_SESSION_ERROR_CODE message:message result:result]; - return; - } - NSMutableDictionary *processedResponse = [[NSMutableDictionary alloc] init]; - [processedResponse setObject:endSessionResponse.state forKey:@"state"]; - result(processedResponse); - }]; + return [OIDAuthorizationService + presentEndSessionRequest:endSessionRequest + externalUserAgent:externalUserAgent + callback:^( + OIDEndSessionResponse *_Nullable endSessionResponse, + NSError *_Nullable error) { + if (!endSessionResponse) { + NSString *message = [NSString + stringWithFormat:END_SESSION_ERROR_MESSAGE_FORMAT, + [error localizedDescription]]; + [FlutterAppAuth finishWithError:END_SESSION_ERROR_CODE + message:message + result:result]; + return; + } + NSMutableDictionary *processedResponse = + [[NSMutableDictionary alloc] init]; + [processedResponse setObject:endSessionResponse.state + forKey:@"state"]; + result(processedResponse); + }]; } -- (id)userAgentWithViewController:(UIViewController *)rootViewController useEphemeralSession:(BOOL)useEphemeralSession { - if (useEphemeralSession) { - return [[OIDExternalUserAgentIOSNoSSO alloc] - initWithPresentingViewController:rootViewController]; - } - return [[OIDExternalUserAgentIOS alloc] - initWithPresentingViewController:rootViewController]; +- (id) + userAgentWithViewController:(UIViewController *)rootViewController + useEphemeralSession:(BOOL)useEphemeralSession { + if (useEphemeralSession) { + return [[OIDExternalUserAgentIOSNoSSO alloc] + initWithPresentingViewController:rootViewController]; + } + return [[OIDExternalUserAgentIOS alloc] + initWithPresentingViewController:rootViewController]; } - (UIViewController *)rootViewController { - if (@available(iOS 13, *)) { - return [[UIApplication sharedApplication].windows filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id window, NSDictionary *bindings) { - return [window isKeyWindow]; - }]].firstObject.rootViewController; - } - return [UIApplication sharedApplication].delegate.window.rootViewController; - + if (@available(iOS 13, *)) { + return [[UIApplication sharedApplication].windows + filteredArrayUsingPredicate:[NSPredicate + predicateWithBlock:^BOOL( + id window, + NSDictionary *bindings) { + return [window isKeyWindow]; + }]] + .firstObject.rootViewController; + } + return [UIApplication sharedApplication].delegate.window.rootViewController; } @end diff --git a/flutter_appauth/ios/Classes/FlutterAppAuth.h b/flutter_appauth/ios/Classes/FlutterAppAuth.h index c8d0669b..e044b8a0 100644 --- a/flutter_appauth/ios/Classes/FlutterAppAuth.h +++ b/flutter_appauth/ios/Classes/FlutterAppAuth.h @@ -11,25 +11,35 @@ NS_ASSUME_NONNULL_BEGIN @interface FlutterAppAuth : NSObject -+ (NSMutableDictionary *)processResponses:(OIDTokenResponse*) tokenResponse authResponse:(OIDAuthorizationResponse* _Nullable) authResponse; -+ (void)finishWithError:(NSString *)errorCode message:(NSString *)message result:(FlutterResult)result; -+ (NSString *) formatMessageWithError:(NSString *)messageFormat error:(NSError * _Nullable)error; ++ (NSMutableDictionary *)processResponses:(OIDTokenResponse *)tokenResponse + authResponse:(OIDAuthorizationResponse *_Nullable) + authResponse; ++ (void)finishWithError:(NSString *)errorCode + message:(NSString *)message + result:(FlutterResult)result; ++ (NSString *)formatMessageWithError:(NSString *)messageFormat + error:(NSError *_Nullable)error; @end static NSString *const AUTHORIZE_METHOD = @"authorize"; -static NSString *const AUTHORIZE_AND_EXCHANGE_CODE_METHOD = @"authorizeAndExchangeCode"; +static NSString *const AUTHORIZE_AND_EXCHANGE_CODE_METHOD = + @"authorizeAndExchangeCode"; static NSString *const TOKEN_METHOD = @"token"; static NSString *const END_SESSION_METHOD = @"endSession"; static NSString *const AUTHORIZE_ERROR_CODE = @"authorize_failed"; -static NSString *const AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE = @"authorize_and_exchange_code_failed"; +static NSString *const AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE = + @"authorize_and_exchange_code_failed"; static NSString *const DISCOVERY_ERROR_CODE = @"discovery_failed"; static NSString *const TOKEN_ERROR_CODE = @"token_failed"; static NSString *const END_SESSION_ERROR_CODE = @"end_session_failed"; -static NSString *const DISCOVERY_ERROR_MESSAGE_FORMAT = @"Error retrieving discovery document: %@"; +static NSString *const DISCOVERY_ERROR_MESSAGE_FORMAT = + @"Error retrieving discovery document: %@"; static NSString *const TOKEN_ERROR_MESSAGE_FORMAT = @"Failed to get token: %@"; -static NSString *const AUTHORIZE_ERROR_MESSAGE_FORMAT = @"Failed to authorize: %@"; -static NSString *const END_SESSION_ERROR_MESSAGE_FORMAT = @"Failed to end session: %@"; +static NSString *const AUTHORIZE_ERROR_MESSAGE_FORMAT = + @"Failed to authorize: %@"; +static NSString *const END_SESSION_ERROR_MESSAGE_FORMAT = + @"Failed to end session: %@"; @interface EndSessionRequestParameters : NSObject @property(nonatomic, strong) NSString *idTokenHint; @@ -44,9 +54,22 @@ static NSString *const END_SESSION_ERROR_MESSAGE_FORMAT = @"Failed to end sessio @interface AppAuthAuthorization : NSObject -- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce; +- (id) + performAuthorization:(OIDServiceConfiguration *)serviceConfiguration + clientId:(NSString *)clientId + clientSecret:(NSString *)clientSecret + scopes:(NSArray *)scopes + redirectUrl:(NSString *)redirectUrl + additionalParameters:(NSDictionary *)additionalParameters + preferEphemeralSession:(BOOL)preferEphemeralSession + result:(FlutterResult)result + exchangeCode:(BOOL)exchangeCode + nonce:(NSString *)nonce; -- (id)performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration requestParameters:(EndSessionRequestParameters *)requestParameters result:(FlutterResult)result; +- (id) + performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration + requestParameters:(EndSessionRequestParameters *)requestParameters + result:(FlutterResult)result; @end diff --git a/flutter_appauth/ios/Classes/FlutterAppAuth.m b/flutter_appauth/ios/Classes/FlutterAppAuth.m index 4e719fb4..be9477c8 100644 --- a/flutter_appauth/ios/Classes/FlutterAppAuth.m +++ b/flutter_appauth/ios/Classes/FlutterAppAuth.m @@ -2,60 +2,90 @@ @implementation FlutterAppAuth -+ (NSMutableDictionary *)processResponses:(OIDTokenResponse*) tokenResponse authResponse:(OIDAuthorizationResponse*) authResponse { - NSMutableDictionary *processedResponses = [[NSMutableDictionary alloc] init]; - if(tokenResponse.accessToken) { - [processedResponses setValue:tokenResponse.accessToken forKey:@"accessToken"]; ++ (NSMutableDictionary *)processResponses:(OIDTokenResponse *)tokenResponse + authResponse: + (OIDAuthorizationResponse *)authResponse { + NSMutableDictionary *processedResponses = [[NSMutableDictionary alloc] init]; + if (tokenResponse.accessToken) { + [processedResponses setValue:tokenResponse.accessToken + forKey:@"accessToken"]; + } + if (tokenResponse.accessTokenExpirationDate) { + [processedResponses + setValue:[[NSNumber alloc] + initWithDouble:[tokenResponse.accessTokenExpirationDate + timeIntervalSince1970] * + 1000] + forKey:@"accessTokenExpirationTime"]; + } + if (authResponse) { + if (authResponse.additionalParameters) { + [processedResponses setObject:authResponse.additionalParameters + forKey:@"authorizationAdditionalParameters"]; } - if(tokenResponse.accessTokenExpirationDate) { - [processedResponses setValue:[[NSNumber alloc] initWithDouble:[tokenResponse.accessTokenExpirationDate timeIntervalSince1970] * 1000] forKey:@"accessTokenExpirationTime"]; + if (authResponse.request && authResponse.request.nonce) { + [processedResponses setObject:authResponse.request.nonce forKey:@"nonce"]; } - if(authResponse) { - if (authResponse.additionalParameters) { - [processedResponses setObject:authResponse.additionalParameters forKey:@"authorizationAdditionalParameters"]; - } - if (authResponse.request && authResponse.request.nonce) { - [processedResponses setObject:authResponse.request.nonce forKey:@"nonce"]; - } - } - if(tokenResponse.additionalParameters) { - [processedResponses setObject:tokenResponse.additionalParameters forKey:@"tokenAdditionalParameters"]; - } - if(tokenResponse.idToken) { - [processedResponses setValue:tokenResponse.idToken forKey:@"idToken"]; - } - if(tokenResponse.refreshToken) { - [processedResponses setValue:tokenResponse.refreshToken forKey:@"refreshToken"]; - } - if(tokenResponse.tokenType) { - [processedResponses setValue:tokenResponse.tokenType forKey:@"tokenType"]; - } - if (tokenResponse.scope) { - [processedResponses setObject:[tokenResponse.scope componentsSeparatedByString: @" "] forKey:@"scopes"]; - } - - return processedResponses; + } + if (tokenResponse.additionalParameters) { + [processedResponses setObject:tokenResponse.additionalParameters + forKey:@"tokenAdditionalParameters"]; + } + if (tokenResponse.idToken) { + [processedResponses setValue:tokenResponse.idToken forKey:@"idToken"]; + } + if (tokenResponse.refreshToken) { + [processedResponses setValue:tokenResponse.refreshToken + forKey:@"refreshToken"]; + } + if (tokenResponse.tokenType) { + [processedResponses setValue:tokenResponse.tokenType forKey:@"tokenType"]; + } + if (tokenResponse.scope) { + [processedResponses + setObject:[tokenResponse.scope componentsSeparatedByString:@" "] + forKey:@"scopes"]; + } + + return processedResponses; } -+ (void)finishWithError:(NSString *)errorCode message:(NSString *)message result:(FlutterResult)result { - result([FlutterError errorWithCode:errorCode message:message details:nil]); ++ (void)finishWithError:(NSString *)errorCode + message:(NSString *)message + result:(FlutterResult)result { + result([FlutterError errorWithCode:errorCode message:message details:nil]); } -+ (NSString *) formatMessageWithError:(NSString *)messageFormat error:(NSError * _Nullable)error { - NSString *formattedMessage = [NSString stringWithFormat:messageFormat, [error localizedDescription]]; - return formattedMessage; ++ (NSString *)formatMessageWithError:(NSString *)messageFormat + error:(NSError *_Nullable)error { + NSString *formattedMessage = + [NSString stringWithFormat:messageFormat, [error localizedDescription]]; + return formattedMessage; } @end @implementation AppAuthAuthorization -- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce { - return nil; +- (id) + performAuthorization:(OIDServiceConfiguration *)serviceConfiguration + clientId:(NSString *)clientId + clientSecret:(NSString *)clientSecret + scopes:(NSArray *)scopes + redirectUrl:(NSString *)redirectUrl + additionalParameters:(NSDictionary *)additionalParameters + preferEphemeralSession:(BOOL)preferEphemeralSession + result:(FlutterResult)result + exchangeCode:(BOOL)exchangeCode + nonce:(NSString *)nonce { + return nil; } -- (id)performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration requestParameters:(EndSessionRequestParameters *)requestParameters result:(FlutterResult)result { - return nil; +- (id) + performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration + requestParameters:(EndSessionRequestParameters *)requestParameters + result:(FlutterResult)result { + return nil; } @end diff --git a/flutter_appauth/ios/Classes/FlutterAppauthPlugin.h b/flutter_appauth/ios/Classes/FlutterAppauthPlugin.h index a2c96d89..13428373 100644 --- a/flutter_appauth/ios/Classes/FlutterAppauthPlugin.h +++ b/flutter_appauth/ios/Classes/FlutterAppauthPlugin.h @@ -1,17 +1,18 @@ #import #if TARGET_OS_OSX -#import #import "AppAuthMacOSAuthorization.h" +#import #else -#import #import "AppAuthIOSAuthorization.h" +#import #endif #import -@interface FlutterAppauthPlugin : NSObject +@interface FlutterAppauthPlugin : NSObject -@property(nonatomic, strong, nullable) id currentAuthorizationFlow; +@property(nonatomic, strong, nullable) id + currentAuthorizationFlow; @end diff --git a/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m b/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m index bf6f6261..3ab0eae8 100644 --- a/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m +++ b/flutter_appauth/ios/Classes/FlutterAppauthPlugin.m @@ -3,13 +3,15 @@ #import "FlutterAppauthPlugin.h" @interface ArgumentProcessor : NSObject -+ (id _Nullable)processArgumentValue:(NSDictionary *)arguments withKey:(NSString *)key; ++ (id _Nullable)processArgumentValue:(NSDictionary *)arguments + withKey:(NSString *)key; @end @implementation ArgumentProcessor -+ (id _Nullable)processArgumentValue:(NSDictionary *)arguments withKey:(NSString *)key { - return [arguments objectForKey:key] != [NSNull null] ? arguments[key] : nil; ++ (id _Nullable)processArgumentValue:(NSDictionary *)arguments + withKey:(NSString *)key { + return [arguments objectForKey:key] != [NSNull null] ? arguments[key] : nil; } @end @@ -34,25 +36,42 @@ @interface TokenRequestParameters : NSObject @implementation TokenRequestParameters - (void)processArguments:(NSDictionary *)arguments { - _clientId = [ArgumentProcessor processArgumentValue:arguments withKey:@"clientId"]; - _clientSecret = [ArgumentProcessor processArgumentValue:arguments withKey:@"clientSecret"]; - _issuer = [ArgumentProcessor processArgumentValue:arguments withKey:@"issuer"]; - _discoveryUrl = [ArgumentProcessor processArgumentValue:arguments withKey:@"discoveryUrl"]; - _redirectUrl = [ArgumentProcessor processArgumentValue:arguments withKey:@"redirectUrl"]; - _refreshToken = [ArgumentProcessor processArgumentValue:arguments withKey:@"refreshToken"]; - _nonce = [ArgumentProcessor processArgumentValue:arguments withKey:@"nonce"]; - _authorizationCode = [ArgumentProcessor processArgumentValue:arguments withKey:@"authorizationCode"]; - _codeVerifier = [ArgumentProcessor processArgumentValue:arguments withKey:@"codeVerifier"]; - _grantType = [ArgumentProcessor processArgumentValue:arguments withKey:@"grantType"]; - _scopes = [ArgumentProcessor processArgumentValue:arguments withKey:@"scopes"]; - _serviceConfigurationParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"serviceConfiguration"]; - _additionalParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"additionalParameters"]; - _preferEphemeralSession = [[ArgumentProcessor processArgumentValue:arguments withKey:@"preferEphemeralSession"] isEqual:@YES]; + _clientId = [ArgumentProcessor processArgumentValue:arguments + withKey:@"clientId"]; + _clientSecret = [ArgumentProcessor processArgumentValue:arguments + withKey:@"clientSecret"]; + _issuer = [ArgumentProcessor processArgumentValue:arguments + withKey:@"issuer"]; + _discoveryUrl = [ArgumentProcessor processArgumentValue:arguments + withKey:@"discoveryUrl"]; + _redirectUrl = [ArgumentProcessor processArgumentValue:arguments + withKey:@"redirectUrl"]; + _refreshToken = [ArgumentProcessor processArgumentValue:arguments + withKey:@"refreshToken"]; + _nonce = [ArgumentProcessor processArgumentValue:arguments withKey:@"nonce"]; + _authorizationCode = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"authorizationCode"]; + _codeVerifier = [ArgumentProcessor processArgumentValue:arguments + withKey:@"codeVerifier"]; + _grantType = [ArgumentProcessor processArgumentValue:arguments + withKey:@"grantType"]; + _scopes = [ArgumentProcessor processArgumentValue:arguments + withKey:@"scopes"]; + _serviceConfigurationParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"serviceConfiguration"]; + _additionalParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"additionalParameters"]; + _preferEphemeralSession = [[ArgumentProcessor + processArgumentValue:arguments + withKey:@"preferEphemeralSession"] isEqual:@YES]; } - (id)initWithArguments:(NSDictionary *)arguments { - [self processArguments:arguments]; - return self; + [self processArguments:arguments]; + return self; } @end @@ -65,251 +84,427 @@ @interface AuthorizationTokenRequestParameters : TokenRequestParameters @implementation AuthorizationTokenRequestParameters - (id)initWithArguments:(NSDictionary *)arguments { - [super processArguments:arguments]; - _loginHint = [ArgumentProcessor processArgumentValue:arguments withKey:@"loginHint"]; - _promptValues = [ArgumentProcessor processArgumentValue:arguments withKey:@"promptValues"]; - _responseMode = [ArgumentProcessor processArgumentValue:arguments withKey:@"responseMode"]; - return self; + [super processArguments:arguments]; + _loginHint = [ArgumentProcessor processArgumentValue:arguments + withKey:@"loginHint"]; + _promptValues = [ArgumentProcessor processArgumentValue:arguments + withKey:@"promptValues"]; + _responseMode = [ArgumentProcessor processArgumentValue:arguments + withKey:@"responseMode"]; + return self; } @end @implementation EndSessionRequestParameters - (id)initWithArguments:(NSDictionary *)arguments { - _idTokenHint= [ArgumentProcessor processArgumentValue:arguments withKey:@"idTokenHint"]; - _postLogoutRedirectUrl = [ArgumentProcessor processArgumentValue:arguments withKey:@"postLogoutRedirectUrl"]; - _state = [ArgumentProcessor processArgumentValue:arguments withKey:@"state"]; - _issuer = [ArgumentProcessor processArgumentValue:arguments withKey:@"issuer"]; - _discoveryUrl = [ArgumentProcessor processArgumentValue:arguments withKey:@"discoveryUrl"]; - _serviceConfigurationParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"serviceConfiguration"]; - _additionalParameters = [ArgumentProcessor processArgumentValue:arguments withKey:@"additionalParameters"]; - _preferEphemeralSession = [[ArgumentProcessor processArgumentValue:arguments withKey:@"preferEphemeralSession"] isEqual:@YES]; - return self; + _idTokenHint = [ArgumentProcessor processArgumentValue:arguments + withKey:@"idTokenHint"]; + _postLogoutRedirectUrl = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"postLogoutRedirectUrl"]; + _state = [ArgumentProcessor processArgumentValue:arguments withKey:@"state"]; + _issuer = [ArgumentProcessor processArgumentValue:arguments + withKey:@"issuer"]; + _discoveryUrl = [ArgumentProcessor processArgumentValue:arguments + withKey:@"discoveryUrl"]; + _serviceConfigurationParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"serviceConfiguration"]; + _additionalParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"additionalParameters"]; + _preferEphemeralSession = [[ArgumentProcessor + processArgumentValue:arguments + withKey:@"preferEphemeralSession"] isEqual:@YES]; + return self; } @end @implementation FlutterAppauthPlugin -FlutterMethodChannel* channel; -AppAuthAuthorization* authorization; +FlutterMethodChannel *channel; +AppAuthAuthorization *authorization; + ++ (void)registerWithRegistrar:(NSObject *)registrar { + channel = [FlutterMethodChannel + methodChannelWithName:@"crossingthestreams.io/flutter_appauth" + binaryMessenger:[registrar messenger]]; + FlutterAppauthPlugin *instance = [[FlutterAppauthPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; -+ (void)registerWithRegistrar:(NSObject*)registrar { - channel = [FlutterMethodChannel - methodChannelWithName:@"crossingthestreams.io/flutter_appauth" - binaryMessenger:[registrar messenger]]; - FlutterAppauthPlugin* instance = [[FlutterAppauthPlugin alloc] init]; - [registrar addMethodCallDelegate:instance channel:channel]; - #if TARGET_OS_OSX - authorization = [[AppAuthMacOSAuthorization alloc] init]; - - NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; - [appleEventManager setEventHandler:instance - andSelector:@selector(handleGetURLEvent:withReplyEvent:) - forEventClass:kInternetEventClass - andEventID:kAEGetURL]; + authorization = [[AppAuthMacOSAuthorization alloc] init]; + + NSAppleEventManager *appleEventManager = + [NSAppleEventManager sharedAppleEventManager]; + [appleEventManager setEventHandler:instance + andSelector:@selector(handleGetURLEvent: + withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; #else - authorization = [[AppAuthIOSAuthorization alloc] init]; - - [registrar addApplicationDelegate:instance]; + authorization = [[AppAuthIOSAuthorization alloc] init]; + + [registrar addApplicationDelegate:instance]; #endif } -- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - if ([AUTHORIZE_AND_EXCHANGE_CODE_METHOD isEqualToString:call.method]) { - [self handleAuthorizeMethodCall:[call arguments] result:result exchangeCode:true]; - } else if([AUTHORIZE_METHOD isEqualToString:call.method]) { - [self handleAuthorizeMethodCall:[call arguments] result:result exchangeCode:false]; - } else if([TOKEN_METHOD isEqualToString:call.method]) { - [self handleTokenMethodCall:[call arguments] result:result]; - } else if([END_SESSION_METHOD isEqualToString:call.method]) { - [self handleEndSessionMethodCall:[call arguments] result:result]; - } else { - result(FlutterMethodNotImplemented); - } +- (void)handleMethodCall:(FlutterMethodCall *)call + result:(FlutterResult)result { + if ([AUTHORIZE_AND_EXCHANGE_CODE_METHOD isEqualToString:call.method]) { + [self handleAuthorizeMethodCall:[call arguments] + result:result + exchangeCode:true]; + } else if ([AUTHORIZE_METHOD isEqualToString:call.method]) { + [self handleAuthorizeMethodCall:[call arguments] + result:result + exchangeCode:false]; + } else if ([TOKEN_METHOD isEqualToString:call.method]) { + [self handleTokenMethodCall:[call arguments] result:result]; + } else if ([END_SESSION_METHOD isEqualToString:call.method]) { + [self handleEndSessionMethodCall:[call arguments] result:result]; + } else { + result(FlutterMethodNotImplemented); + } } -- (void)ensureAdditionalParametersInitialized:(AuthorizationTokenRequestParameters *)requestParameters { - if(!requestParameters.additionalParameters) { - requestParameters.additionalParameters = [[NSMutableDictionary alloc] init]; - } +- (void)ensureAdditionalParametersInitialized: + (AuthorizationTokenRequestParameters *)requestParameters { + if (!requestParameters.additionalParameters) { + requestParameters.additionalParameters = [[NSMutableDictionary alloc] init]; + } } --(void)handleAuthorizeMethodCall:(NSDictionary*)arguments result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode { - AuthorizationTokenRequestParameters *requestParameters = [[AuthorizationTokenRequestParameters alloc] initWithArguments:arguments]; - [self ensureAdditionalParametersInitialized:requestParameters]; - if(requestParameters.loginHint) { - [requestParameters.additionalParameters setValue:requestParameters.loginHint forKey:@"login_hint"]; - } - if(requestParameters.promptValues) { - [requestParameters.additionalParameters setValue:[requestParameters.promptValues componentsJoinedByString:@" "] forKey:@"prompt"]; - } - if(requestParameters.responseMode) { - [requestParameters.additionalParameters setValue:requestParameters.responseMode forKey:@"response_mode"]; - } - - if(requestParameters.serviceConfigurationParameters != nil) { - OIDServiceConfiguration *serviceConfiguration = [self processServiceConfigurationParameters:requestParameters.serviceConfigurationParameters]; - _currentAuthorizationFlow = [authorization performAuthorization:serviceConfiguration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferEphemeralSession:requestParameters.preferEphemeralSession result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; - } else if (requestParameters.discoveryUrl) { - NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; - [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:discoveryUrl - completion:^(OIDServiceConfiguration *_Nullable configuration, - NSError *_Nullable error) { - - if (!configuration) { - [self finishWithDiscoveryError:error result:result]; - return; - } - - self->_currentAuthorizationFlow = [authorization performAuthorization:configuration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferEphemeralSession:requestParameters.preferEphemeralSession result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; - }]; - } else { - NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; - [OIDAuthorizationService discoverServiceConfigurationForIssuer:issuerUrl - completion:^(OIDServiceConfiguration *_Nullable configuration, - NSError *_Nullable error) { - - if (!configuration) { - [self finishWithDiscoveryError:error result:result]; - return; - } - - self->_currentAuthorizationFlow = [authorization performAuthorization:configuration clientId:requestParameters.clientId clientSecret:requestParameters.clientSecret scopes:requestParameters.scopes redirectUrl:requestParameters.redirectUrl additionalParameters:requestParameters.additionalParameters preferEphemeralSession:requestParameters.preferEphemeralSession result:result exchangeCode:exchangeCode nonce:requestParameters.nonce]; - }]; - } +- (void)handleAuthorizeMethodCall:(NSDictionary *)arguments + result:(FlutterResult)result + exchangeCode:(BOOL)exchangeCode { + AuthorizationTokenRequestParameters *requestParameters = + [[AuthorizationTokenRequestParameters alloc] initWithArguments:arguments]; + [self ensureAdditionalParametersInitialized:requestParameters]; + if (requestParameters.loginHint) { + [requestParameters.additionalParameters setValue:requestParameters.loginHint + forKey:@"login_hint"]; + } + if (requestParameters.promptValues) { + [requestParameters.additionalParameters + setValue:[requestParameters.promptValues componentsJoinedByString:@" "] + forKey:@"prompt"]; + } + if (requestParameters.responseMode) { + [requestParameters.additionalParameters + setValue:requestParameters.responseMode + forKey:@"response_mode"]; + } + + if (requestParameters.serviceConfigurationParameters != nil) { + OIDServiceConfiguration *serviceConfiguration = + [self processServiceConfigurationParameters: + requestParameters.serviceConfigurationParameters]; + _currentAuthorizationFlow = [authorization + performAuthorization:serviceConfiguration + clientId:requestParameters.clientId + clientSecret:requestParameters.clientSecret + scopes:requestParameters.scopes + redirectUrl:requestParameters.redirectUrl + additionalParameters:requestParameters.additionalParameters + preferEphemeralSession:requestParameters.preferEphemeralSession + result:result + exchangeCode:exchangeCode + nonce:requestParameters.nonce]; + } else if (requestParameters.discoveryUrl) { + NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; + [OIDAuthorizationService + discoverServiceConfigurationForDiscoveryURL:discoveryUrl + completion:^( + OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self + finishWithDiscoveryError:error + result: + result]; + return; + } + + self->_currentAuthorizationFlow = [authorization + performAuthorization: + configuration + clientId: + requestParameters + .clientId + clientSecret: + requestParameters + .clientSecret + scopes: + requestParameters + .scopes + redirectUrl: + requestParameters + .redirectUrl + additionalParameters: + requestParameters + .additionalParameters + preferEphemeralSession: + requestParameters + .preferEphemeralSession + result:result + exchangeCode: + exchangeCode + nonce: + requestParameters + .nonce]; + }]; + } else { + NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; + [OIDAuthorizationService + discoverServiceConfigurationForIssuer:issuerUrl + completion:^(OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self finishWithDiscoveryError:error + result:result]; + return; + } + + self->_currentAuthorizationFlow = [authorization + performAuthorization:configuration + clientId: + requestParameters + .clientId + clientSecret: + requestParameters + .clientSecret + scopes: + requestParameters + .scopes + redirectUrl: + requestParameters + .redirectUrl + additionalParameters: + requestParameters + .additionalParameters + preferEphemeralSession: + requestParameters + .preferEphemeralSession + result:result + exchangeCode:exchangeCode + nonce: + requestParameters + .nonce]; + }]; + } } -- (void)finishWithDiscoveryError:(NSError * _Nullable)error result:(FlutterResult)result { - NSString *message = [NSString stringWithFormat:DISCOVERY_ERROR_MESSAGE_FORMAT, [error localizedDescription]]; - [FlutterAppAuth finishWithError:DISCOVERY_ERROR_CODE message:message result:result]; +- (void)finishWithDiscoveryError:(NSError *_Nullable)error + result:(FlutterResult)result { + NSString *message = [NSString stringWithFormat:DISCOVERY_ERROR_MESSAGE_FORMAT, + [error localizedDescription]]; + [FlutterAppAuth finishWithError:DISCOVERY_ERROR_CODE + message:message + result:result]; } - -- (OIDServiceConfiguration *)processServiceConfigurationParameters:(NSDictionary*)serviceConfigurationParameters { - NSURL *endSessionEndpoint = serviceConfigurationParameters[@"endSessionEndpoint"] == [NSNull null] ? nil : [NSURL URLWithString:serviceConfigurationParameters[@"endSessionEndpoint"]]; - OIDServiceConfiguration *serviceConfiguration = - [[OIDServiceConfiguration alloc] - initWithAuthorizationEndpoint:[NSURL URLWithString:serviceConfigurationParameters[@"authorizationEndpoint"]] - tokenEndpoint:[NSURL URLWithString:serviceConfigurationParameters[@"tokenEndpoint"]] issuer:nil registrationEndpoint:nil endSessionEndpoint:endSessionEndpoint]; - return serviceConfiguration; +- (OIDServiceConfiguration *)processServiceConfigurationParameters: + (NSDictionary *)serviceConfigurationParameters { + NSURL *endSessionEndpoint = + serviceConfigurationParameters[@"endSessionEndpoint"] == [NSNull null] + ? nil + : [NSURL URLWithString:serviceConfigurationParameters + [@"endSessionEndpoint"]]; + OIDServiceConfiguration *serviceConfiguration = + [[OIDServiceConfiguration alloc] + initWithAuthorizationEndpoint: + [NSURL URLWithString:serviceConfigurationParameters + [@"authorizationEndpoint"]] + tokenEndpoint: + [NSURL + URLWithString:serviceConfigurationParameters + [@"tokenEndpoint"]] + issuer:nil + registrationEndpoint:nil + endSessionEndpoint:endSessionEndpoint]; + return serviceConfiguration; } --(void)handleTokenMethodCall:(NSDictionary*)arguments result:(FlutterResult)result { - TokenRequestParameters *requestParameters = [[TokenRequestParameters alloc] initWithArguments:arguments]; - if(requestParameters.serviceConfigurationParameters != nil) { - OIDServiceConfiguration *serviceConfiguration = [self processServiceConfigurationParameters:requestParameters.serviceConfigurationParameters]; - [self performTokenRequest:serviceConfiguration requestParameters:requestParameters result:result]; - } else if (requestParameters.discoveryUrl) { - NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; - - [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:discoveryUrl - completion:^(OIDServiceConfiguration *_Nullable configuration, - NSError *_Nullable error) { - if (!configuration) { - [self finishWithDiscoveryError:error result:result]; - return; - } - - [self performTokenRequest:configuration requestParameters:requestParameters result:result]; - }]; - } else { - NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; - [OIDAuthorizationService discoverServiceConfigurationForIssuer:issuerUrl - completion:^(OIDServiceConfiguration *_Nullable configuration, - NSError *_Nullable error) { - if (!configuration) { - [self finishWithDiscoveryError:error result:result]; - return; - } - - [self performTokenRequest:configuration requestParameters:requestParameters result:result]; - }]; - } +- (void)handleTokenMethodCall:(NSDictionary *)arguments + result:(FlutterResult)result { + TokenRequestParameters *requestParameters = + [[TokenRequestParameters alloc] initWithArguments:arguments]; + if (requestParameters.serviceConfigurationParameters != nil) { + OIDServiceConfiguration *serviceConfiguration = + [self processServiceConfigurationParameters: + requestParameters.serviceConfigurationParameters]; + [self performTokenRequest:serviceConfiguration + requestParameters:requestParameters + result:result]; + } else if (requestParameters.discoveryUrl) { + NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; + + [OIDAuthorizationService + discoverServiceConfigurationForDiscoveryURL:discoveryUrl + completion:^( + OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self + finishWithDiscoveryError:error + result: + result]; + return; + } + + [self + performTokenRequest:configuration + requestParameters: + requestParameters + result:result]; + }]; + } else { + NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; + [OIDAuthorizationService + discoverServiceConfigurationForIssuer:issuerUrl + completion:^(OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self finishWithDiscoveryError:error + result:result]; + return; + } + + [self performTokenRequest:configuration + requestParameters:requestParameters + result:result]; + }]; + } } --(void)handleEndSessionMethodCall:(NSDictionary*)arguments result:(FlutterResult)result { - EndSessionRequestParameters *requestParameters = [[EndSessionRequestParameters alloc] initWithArguments:arguments]; - if(requestParameters.serviceConfigurationParameters != nil) { - OIDServiceConfiguration *serviceConfiguration = [self processServiceConfigurationParameters:requestParameters.serviceConfigurationParameters]; - _currentAuthorizationFlow = [authorization performEndSessionRequest:serviceConfiguration requestParameters:requestParameters result:result]; - } else if (requestParameters.discoveryUrl) { - NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; - - [OIDAuthorizationService discoverServiceConfigurationForDiscoveryURL:discoveryUrl - completion:^(OIDServiceConfiguration *_Nullable configuration, - NSError *_Nullable error) { - if (!configuration) { - [self finishWithDiscoveryError:error result:result]; - return; - } - - self->_currentAuthorizationFlow = [authorization performEndSessionRequest:configuration requestParameters:requestParameters result:result]; - }]; - } else { - NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; - [OIDAuthorizationService discoverServiceConfigurationForIssuer:issuerUrl - completion:^(OIDServiceConfiguration *_Nullable configuration, - NSError *_Nullable error) { - if (!configuration) { - [self finishWithDiscoveryError:error result:result]; - return; - } - - self->_currentAuthorizationFlow = [authorization performEndSessionRequest:configuration requestParameters:requestParameters result:result]; - }]; - } +- (void)handleEndSessionMethodCall:(NSDictionary *)arguments + result:(FlutterResult)result { + EndSessionRequestParameters *requestParameters = + [[EndSessionRequestParameters alloc] initWithArguments:arguments]; + if (requestParameters.serviceConfigurationParameters != nil) { + OIDServiceConfiguration *serviceConfiguration = + [self processServiceConfigurationParameters: + requestParameters.serviceConfigurationParameters]; + _currentAuthorizationFlow = + [authorization performEndSessionRequest:serviceConfiguration + requestParameters:requestParameters + result:result]; + } else if (requestParameters.discoveryUrl) { + NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; + + [OIDAuthorizationService + discoverServiceConfigurationForDiscoveryURL:discoveryUrl + completion:^( + OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self + finishWithDiscoveryError:error + result: + result]; + return; + } + + self->_currentAuthorizationFlow = + [authorization + performEndSessionRequest: + configuration + requestParameters: + requestParameters + result: + result]; + }]; + } else { + NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; + [OIDAuthorizationService + discoverServiceConfigurationForIssuer:issuerUrl + completion:^(OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self finishWithDiscoveryError:error + result:result]; + return; + } + + self->_currentAuthorizationFlow = + [authorization + performEndSessionRequest: + configuration + requestParameters: + requestParameters + result:result]; + }]; + } } -- (void)performTokenRequest:(OIDServiceConfiguration *)serviceConfiguration requestParameters:(TokenRequestParameters *)requestParameters result:(FlutterResult)result { - OIDTokenRequest *tokenRequest = - [[OIDTokenRequest alloc] initWithConfiguration:serviceConfiguration - grantType:requestParameters.grantType - authorizationCode:requestParameters.authorizationCode - redirectURL:[NSURL URLWithString:requestParameters.redirectUrl] - clientID:requestParameters.clientId - clientSecret:requestParameters.clientSecret - scopes:requestParameters.scopes - refreshToken:requestParameters.refreshToken - codeVerifier:requestParameters.codeVerifier - additionalParameters:requestParameters.additionalParameters]; - [OIDAuthorizationService performTokenRequest:tokenRequest - callback:^(OIDTokenResponse *_Nullable response, - NSError *_Nullable error) { - if (response) { - result([FlutterAppAuth processResponses:response authResponse:nil]); } else { - NSString *message = [NSString stringWithFormat:TOKEN_ERROR_MESSAGE_FORMAT, [error localizedDescription]]; - [FlutterAppAuth finishWithError:TOKEN_ERROR_CODE message:message result:result]; - } - }]; +- (void)performTokenRequest:(OIDServiceConfiguration *)serviceConfiguration + requestParameters:(TokenRequestParameters *)requestParameters + result:(FlutterResult)result { + OIDTokenRequest *tokenRequest = [[OIDTokenRequest alloc] + initWithConfiguration:serviceConfiguration + grantType:requestParameters.grantType + authorizationCode:requestParameters.authorizationCode + redirectURL:[NSURL URLWithString:requestParameters.redirectUrl] + clientID:requestParameters.clientId + clientSecret:requestParameters.clientSecret + scopes:requestParameters.scopes + refreshToken:requestParameters.refreshToken + codeVerifier:requestParameters.codeVerifier + additionalParameters:requestParameters.additionalParameters]; + [OIDAuthorizationService + performTokenRequest:tokenRequest + callback:^(OIDTokenResponse *_Nullable response, + NSError *_Nullable error) { + if (response) { + result([FlutterAppAuth processResponses:response + authResponse:nil]); + } else { + NSString *message = [NSString + stringWithFormat:TOKEN_ERROR_MESSAGE_FORMAT, + [error localizedDescription]]; + [FlutterAppAuth finishWithError:TOKEN_ERROR_CODE + message:message + result:result]; + } + }]; } #if TARGET_OS_IOS - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url - options:(NSDictionary *)options { - if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { - _currentAuthorizationFlow = nil; - return YES; - } - - return NO; + options: + (NSDictionary *)options { + if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { + _currentAuthorizationFlow = nil; + return YES; + } + + return NO; } - (BOOL)application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(NSString *)sourceApplication - annotation:(id)annotation { - return [self application:application openURL:url options:@{}]; + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + return [self application:application openURL:url options:@{}]; } #endif #if TARGET_OS_OSX - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { - NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - NSURL *URL = [NSURL URLWithString:URLString]; - [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL]; - _currentAuthorizationFlow = nil; + NSString *URLString = + [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + NSURL *URL = [NSURL URLWithString:URLString]; + [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL]; + _currentAuthorizationFlow = nil; } #endif diff --git a/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.h b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.h index 5d8c9de7..c83c559b 100644 --- a/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.h +++ b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.h @@ -1,7 +1,9 @@ /*! @file OIDExternalUserAgentIOSNoSSO.h - @brief OIDExternalUserAgentIOSNoSSO is a custom user agent based on the default user agent in the AppAuth iOS SDK found here: + @brief OIDExternalUserAgentIOSNoSSO is a custom user agent based on the + default user agent in the AppAuth iOS SDK found here: https://github.com/openid/AppAuth-iOS/blob/master/Source/iOS/OIDExternalUserAgentIOS.h - Ths user agent allows setting `prefersEphemeralSession` flag on iOS 13 or newer to avoid cookies being shared across the device. + Ths user agent allows setting `prefersEphemeralSession` flag on iOS + 13 or newer to avoid cookies being shared across the device. @copydetails Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,27 +18,26 @@ limitations under the License. */ -#import #import +#import @class SFSafariViewController; NS_ASSUME_NONNULL_BEGIN API_UNAVAILABLE(macCatalyst) -@interface OIDExternalUserAgentIOSNoSSO : NSObject +@interface OIDExternalUserAgentIOSNoSSO : NSObject -- (nullable instancetype)init API_AVAILABLE(ios(11)) - __deprecated_msg("This method will not work on iOS 13, use " - "initWithPresentingViewController:presentingViewController"); +- (nullable instancetype)init API_AVAILABLE(ios(11))__deprecated_msg( + "This method will not work on iOS 13, use " + "initWithPresentingViewController:presentingViewController"); /*! @brief The designated initializer. - @param presentingViewController The view controller from which to present the - \SFSafariViewController. + @param presentingViewController The view controller from which to present + the \SFSafariViewController. */ - (nullable instancetype)initWithPresentingViewController: - (UIViewController *)presentingViewController - NS_DESIGNATED_INITIALIZER; + (UIViewController *)presentingViewController NS_DESIGNATED_INITIALIZER; @end diff --git a/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.m b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.m index a4e018ea..1ded133d 100644 --- a/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.m +++ b/flutter_appauth/ios/Classes/OIDExternalUserAgentIOSNoSSO.m @@ -1,24 +1,27 @@ /*! @file OIDExternalUserAgentIOSNoSSO.m - @brief OIDExternalUserAgentIOSNoSSO is a custom user agent based on the default user agent in the AppAuth iOS SDK found here: + @brief OIDExternalUserAgentIOSNoSSO is a custom user agent based on the + default user agent in the AppAuth iOS SDK found here: https://github.com/openid/AppAuth-iOS/blob/master/Source/iOS/OIDExternalUserAgentIOS.m - This user agent allows setting `prefersEphemeralSession` flag on iOS 13 or newer to avoid cookies being shared across the device. + This user agent allows setting `prefersEphemeralSession` flag on iOS + 13 or newer to avoid cookies being shared across the device. */ #import "OIDExternalUserAgentIOSNoSSO.h" -#import #import - +#import #if !TARGET_OS_MACCATALYST NS_ASSUME_NONNULL_BEGIN #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 -@interface OIDExternalUserAgentIOSNoSSO () +@interface OIDExternalUserAgentIOSNoSSO () < + SFSafariViewControllerDelegate, + ASWebAuthenticationPresentationContextProviding> @end #else -@interface OIDExternalUserAgentIOSNoSSO () +@interface OIDExternalUserAgentIOSNoSSO () @end #endif @@ -50,14 +53,15 @@ - (nullable instancetype)initWithPresentingViewController: NSAssert(presentingViewController != nil, @"presentingViewController cannot be nil on iOS 13"); #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 - + _presentingViewController = presentingViewController; } return self; } - (BOOL)presentExternalUserAgentRequest:(id)request - session:(id)session { + session: + (id)session { if (_externalUserAgentFlowInProgress) { // TODO: Handle errors as authorization is already in progress. return NO; @@ -70,39 +74,43 @@ - (BOOL)presentExternalUserAgentRequest:(id)request // iOS 12 and later, use ASWebAuthenticationSession if (@available(iOS 12.0, *)) { - // ASWebAuthenticationSession doesn't work with guided access (rdar://40809553) + // ASWebAuthenticationSession doesn't work with guided access + // (rdar://40809553) if (!UIAccessibilityIsGuidedAccessEnabled()) { __weak OIDExternalUserAgentIOSNoSSO *weakSelf = self; NSString *redirectScheme = request.redirectScheme; ASWebAuthenticationSession *authenticationVC = - [[ASWebAuthenticationSession alloc] initWithURL:requestURL - callbackURLScheme:redirectScheme - completionHandler:^(NSURL * _Nullable callbackURL, - NSError * _Nullable error) { - __strong OIDExternalUserAgentIOSNoSSO *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - strongSelf->_webAuthenticationVC = nil; - if (callbackURL) { - [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; - } else { - NSError *safariError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow - underlyingError:error - description:nil]; - [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; - } - }]; + [[ASWebAuthenticationSession alloc] + initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL *_Nullable callbackURL, + NSError *_Nullable error) { + __strong OIDExternalUserAgentIOSNoSSO *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationVC = nil; + if (callbackURL) { + [strongSelf->_session + resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session + failExternalUserAgentFlowWithError:safariError]; + } + }]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 if (@available(iOS 13.0, *)) { - authenticationVC.presentationContextProvider = self; + authenticationVC.presentationContextProvider = self; } #endif _webAuthenticationVC = authenticationVC; - if (@available(iOS 13.0, *)) { - authenticationVC.prefersEphemeralWebBrowserSession = YES; - } + if (@available(iOS 13.0, *)) { + authenticationVC.prefersEphemeralWebBrowserSession = YES; + } openedUserAgent = [authenticationVC start]; } } @@ -113,25 +121,28 @@ - (BOOL)presentExternalUserAgentRequest:(id)request __weak OIDExternalUserAgentIOSNoSSO *weakSelf = self; NSString *redirectScheme = request.redirectScheme; SFAuthenticationSession *authenticationVC = - [[SFAuthenticationSession alloc] initWithURL:requestURL - callbackURLScheme:redirectScheme - completionHandler:^(NSURL * _Nullable callbackURL, - NSError * _Nullable error) { - __strong OIDExternalUserAgentIOSNoSSO *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - strongSelf->_authenticationVC = nil; - if (callbackURL) { - [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; - } else { - NSError *safariError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow - underlyingError:error - description:@"User cancelled."]; - [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; - } - }]; + [[SFAuthenticationSession alloc] + initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL *_Nullable callbackURL, + NSError *_Nullable error) { + __strong OIDExternalUserAgentIOSNoSSO *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_authenticationVC = nil; + if (callbackURL) { + [strongSelf->_session + resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:@"User cancelled."]; + [strongSelf->_session + failExternalUserAgentFlowWithError:safariError]; + } + }]; _authenticationVC = authenticationVC; openedUserAgent = [authenticationVC start]; } @@ -143,60 +154,68 @@ - (BOOL)presentExternalUserAgentRequest:(id)request [[SFSafariViewController alloc] initWithURL:requestURL]; safariVC.delegate = self; _safariVC = safariVC; - [_presentingViewController presentViewController:safariVC animated:YES completion:nil]; + [_presentingViewController presentViewController:safariVC + animated:YES + completion:nil]; openedUserAgent = YES; } } // iOS 8 and earlier, use mobile Safari - if (!openedUserAgent){ + if (!openedUserAgent) { openedUserAgent = [[UIApplication sharedApplication] openURL:requestURL]; } if (!openedUserAgent) { [self cleanUp]; - NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError - underlyingError:nil - description:@"Unable to open Safari."]; + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; [session failExternalUserAgentFlowWithError:safariError]; } return openedUserAgent; } -- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { +- (void)dismissExternalUserAgentAnimated:(BOOL)animated + completion:(void (^)(void))completion { if (!_externalUserAgentFlowInProgress) { // Ignore this call if there is no authorization flow in progress. - if (completion) completion(); + if (completion) + completion(); return; } - + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" SFSafariViewController *safariVC = _safariVC; SFAuthenticationSession *authenticationVC = _authenticationVC; ASWebAuthenticationSession *webAuthenticationVC = _webAuthenticationVC; #pragma clang diagnostic pop - + [self cleanUp]; - + if (webAuthenticationVC) { // dismiss the ASWebAuthenticationSession [webAuthenticationVC cancel]; - if (completion) completion(); + if (completion) + completion(); } else if (authenticationVC) { // dismiss the SFAuthenticationSession [authenticationVC cancel]; - if (completion) completion(); + if (completion) + completion(); } else if (safariVC) { // dismiss the SFSafariViewController [safariVC dismissViewControllerAnimated:YES completion:completion]; } else { - if (completion) completion(); + if (completion) + completion(); } } - (void)cleanUp { - // The weak references to |_safariVC| and |_session| are set to nil to avoid accidentally using - // them while not in an authorization flow. + // The weak references to |_safariVC| and |_session| are set to nil to avoid + // accidentally using them while not in an authorization flow. _safariVC = nil; _authenticationVC = nil; _webAuthenticationVC = nil; @@ -206,7 +225,8 @@ - (void)cleanUp { #pragma mark - SFSafariViewControllerDelegate -- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller NS_AVAILABLE_IOS(9.0) { +- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller + NS_AVAILABLE_IOS(9.0) { if (controller != _safariVC) { // Ignore this call if the safari view controller do not match. return; @@ -217,16 +237,18 @@ - (void)safariViewControllerDidFinish:(SFSafariViewController *)controller NS_AV } id session = _session; [self cleanUp]; - NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow - underlyingError:nil - description:@"No external user agent flow in progress."]; + NSError *error = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:@"No external user agent flow in progress."]; [session failExternalUserAgentFlowWithError:error]; } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 #pragma mark - ASWebAuthenticationPresentationContextProviding -- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session API_AVAILABLE(ios(13.0)){ +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession: + (ASWebAuthenticationSession *)session API_AVAILABLE(ios(13.0)) { return _presentingViewController.view.window; } #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 diff --git a/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.h b/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.h index 5f748e95..7bd7d860 100644 --- a/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.h +++ b/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.h @@ -1,8 +1,7 @@ -#import -#import -#import "OIDExternalUserAgentMacNoSSO.h" #import "FlutterAppAuth.h" - +#import "OIDExternalUserAgentMacNoSSO.h" +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m b/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m index f53f21d2..815f6f73 100644 --- a/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m +++ b/flutter_appauth/macos/Classes/AppAuthMacOSAuthorization.m @@ -2,78 +2,171 @@ @implementation AppAuthMacOSAuthorization -- (id)performAuthorization:(OIDServiceConfiguration *)serviceConfiguration clientId:(NSString*)clientId clientSecret:(NSString*)clientSecret scopes:(NSArray *)scopes redirectUrl:(NSString*)redirectUrl additionalParameters:(NSDictionary *)additionalParameters preferEphemeralSession:(BOOL)preferEphemeralSession result:(FlutterResult)result exchangeCode:(BOOL)exchangeCode nonce:(NSString*)nonce { - NSString *codeVerifier = [OIDAuthorizationRequest generateCodeVerifier]; - NSString *codeChallenge = [OIDAuthorizationRequest codeChallengeS256ForVerifier:codeVerifier]; +- (id) + performAuthorization:(OIDServiceConfiguration *)serviceConfiguration + clientId:(NSString *)clientId + clientSecret:(NSString *)clientSecret + scopes:(NSArray *)scopes + redirectUrl:(NSString *)redirectUrl + additionalParameters:(NSDictionary *)additionalParameters + preferEphemeralSession:(BOOL)preferEphemeralSession + result:(FlutterResult)result + exchangeCode:(BOOL)exchangeCode + nonce:(NSString *)nonce { + NSString *codeVerifier = [OIDAuthorizationRequest generateCodeVerifier]; + NSString *codeChallenge = + [OIDAuthorizationRequest codeChallengeS256ForVerifier:codeVerifier]; - OIDAuthorizationRequest *request = - [[OIDAuthorizationRequest alloc] initWithConfiguration:serviceConfiguration - clientId:clientId - clientSecret:clientSecret - scope:[OIDScopeUtilities scopesWithArray:scopes] - redirectURL:[NSURL URLWithString:redirectUrl] - responseType:OIDResponseTypeCode - state:[OIDAuthorizationRequest generateState] - nonce: nonce != nil ? nonce : [OIDAuthorizationRequest generateState] - codeVerifier:codeVerifier - codeChallenge:codeChallenge - codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256 - additionalParameters:additionalParameters]; - NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; - if(exchangeCode) { - NSObject *agent = [self userAgentWithPresentingWindow:keyWindow useEphemeralSession:preferEphemeralSession]; - return [OIDAuthState authStateByPresentingAuthorizationRequest:request externalUserAgent:agent callback:^(OIDAuthState *_Nullable authState, - NSError *_Nullable error) { - if(authState) { - result([FlutterAppAuth processResponses:authState.lastTokenResponse authResponse:authState.lastAuthorizationResponse]); - - } else { - [FlutterAppAuth finishWithError:AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE message:[FlutterAppAuth formatMessageWithError:AUTHORIZE_ERROR_MESSAGE_FORMAT error:error] result:result]; - } - }]; - } else { - NSObject *agent = [self userAgentWithPresentingWindow:keyWindow useEphemeralSession:preferEphemeralSession]; - return [OIDAuthorizationService presentAuthorizationRequest:request externalUserAgent:agent callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) { - if(authorizationResponse) { - NSMutableDictionary *processedResponse = [[NSMutableDictionary alloc] init]; - [processedResponse setObject:authorizationResponse.additionalParameters forKey:@"authorizationAdditionalParameters"]; - [processedResponse setObject:authorizationResponse.authorizationCode forKey:@"authorizationCode"]; - [processedResponse setObject:authorizationResponse.request.codeVerifier forKey:@"codeVerifier"]; - [processedResponse setObject:authorizationResponse.request.nonce forKey:@"nonce"]; - result(processedResponse); - } else { - [FlutterAppAuth finishWithError:AUTHORIZE_ERROR_CODE message:[FlutterAppAuth formatMessageWithError:AUTHORIZE_ERROR_MESSAGE_FORMAT error:error] result:result]; - } - }]; - } + OIDAuthorizationRequest *request = [[OIDAuthorizationRequest alloc] + initWithConfiguration:serviceConfiguration + clientId:clientId + clientSecret:clientSecret + scope:[OIDScopeUtilities scopesWithArray:scopes] + redirectURL:[NSURL URLWithString:redirectUrl] + responseType:OIDResponseTypeCode + state:[OIDAuthorizationRequest generateState] + nonce:nonce != nil + ? nonce + : [OIDAuthorizationRequest generateState] + codeVerifier:codeVerifier + codeChallenge:codeChallenge + codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256 + additionalParameters:additionalParameters]; + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + if (exchangeCode) { + NSObject *agent = + [self userAgentWithPresentingWindow:keyWindow + useEphemeralSession:preferEphemeralSession]; + return [OIDAuthState + authStateByPresentingAuthorizationRequest:request + externalUserAgent:agent + callback:^( + OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + if (authState) { + result([FlutterAppAuth + processResponses: + authState.lastTokenResponse + authResponse: + authState + .lastAuthorizationResponse]); + + } else { + [FlutterAppAuth + finishWithError: + AUTHORIZE_AND_EXCHANGE_CODE_ERROR_CODE + message: + [FlutterAppAuth + formatMessageWithError: + AUTHORIZE_ERROR_MESSAGE_FORMAT + error: + error] + result:result]; + } + }]; + } else { + NSObject *agent = + [self userAgentWithPresentingWindow:keyWindow + useEphemeralSession:preferEphemeralSession]; + return [OIDAuthorizationService + presentAuthorizationRequest:request + externalUserAgent:agent + callback:^(OIDAuthorizationResponse + *_Nullable authorizationResponse, + NSError *_Nullable error) { + if (authorizationResponse) { + NSMutableDictionary *processedResponse = + [[NSMutableDictionary alloc] init]; + [processedResponse + setObject:authorizationResponse + .additionalParameters + forKey: + @"authorizationAdditionalParameters"]; + [processedResponse + setObject:authorizationResponse + .authorizationCode + forKey:@"authorizationCode"]; + [processedResponse + setObject:authorizationResponse.request + .codeVerifier + forKey:@"codeVerifier"]; + [processedResponse + setObject:authorizationResponse.request.nonce + forKey:@"nonce"]; + result(processedResponse); + } else { + [FlutterAppAuth + finishWithError:AUTHORIZE_ERROR_CODE + message: + [FlutterAppAuth + formatMessageWithError: + AUTHORIZE_ERROR_MESSAGE_FORMAT + error:error] + result:result]; + } + }]; + } } -- (id)performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration requestParameters:(EndSessionRequestParameters *)requestParameters result:(FlutterResult)result { - NSURL *postLogoutRedirectURL = requestParameters.postLogoutRedirectUrl ? [NSURL URLWithString:requestParameters.postLogoutRedirectUrl] : nil; - - OIDEndSessionRequest *endSessionRequest = requestParameters.state ? [[OIDEndSessionRequest alloc] initWithConfiguration:serviceConfiguration idTokenHint:requestParameters.idTokenHint postLogoutRedirectURL:postLogoutRedirectURL - state:requestParameters.state additionalParameters:requestParameters.additionalParameters] :[[OIDEndSessionRequest alloc] initWithConfiguration:serviceConfiguration idTokenHint:requestParameters.idTokenHint postLogoutRedirectURL:postLogoutRedirectURL - additionalParameters:requestParameters.additionalParameters]; - - NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; - id externalUserAgent = [self userAgentWithPresentingWindow:keyWindow useEphemeralSession:requestParameters.preferEphemeralSession]; - return [OIDAuthorizationService presentEndSessionRequest:endSessionRequest externalUserAgent:externalUserAgent callback:^(OIDEndSessionResponse * _Nullable endSessionResponse, NSError * _Nullable error) { - if(!endSessionResponse) { - NSString *message = [NSString stringWithFormat:END_SESSION_ERROR_MESSAGE_FORMAT, [error localizedDescription]]; - [FlutterAppAuth finishWithError:END_SESSION_ERROR_CODE message:message result:result]; - return; - } - NSMutableDictionary *processedResponse = [[NSMutableDictionary alloc] init]; - [processedResponse setObject:endSessionResponse.state forKey:@"state"]; - result(processedResponse); - }]; +- (id) + performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration + requestParameters:(EndSessionRequestParameters *)requestParameters + result:(FlutterResult)result { + NSURL *postLogoutRedirectURL = + requestParameters.postLogoutRedirectUrl + ? [NSURL URLWithString:requestParameters.postLogoutRedirectUrl] + : nil; + + OIDEndSessionRequest *endSessionRequest = + requestParameters.state + ? [[OIDEndSessionRequest alloc] + initWithConfiguration:serviceConfiguration + idTokenHint:requestParameters.idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:requestParameters.state + additionalParameters:requestParameters.additionalParameters] + : [[OIDEndSessionRequest alloc] + initWithConfiguration:serviceConfiguration + idTokenHint:requestParameters.idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + additionalParameters:requestParameters.additionalParameters]; + + NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow]; + id externalUserAgent = [self + userAgentWithPresentingWindow:keyWindow + useEphemeralSession:requestParameters.preferEphemeralSession]; + return [OIDAuthorizationService + presentEndSessionRequest:endSessionRequest + externalUserAgent:externalUserAgent + callback:^( + OIDEndSessionResponse *_Nullable endSessionResponse, + NSError *_Nullable error) { + if (!endSessionResponse) { + NSString *message = [NSString + stringWithFormat:END_SESSION_ERROR_MESSAGE_FORMAT, + [error localizedDescription]]; + [FlutterAppAuth finishWithError:END_SESSION_ERROR_CODE + message:message + result:result]; + return; + } + NSMutableDictionary *processedResponse = + [[NSMutableDictionary alloc] init]; + [processedResponse setObject:endSessionResponse.state + forKey:@"state"]; + result(processedResponse); + }]; } -- (id)userAgentWithPresentingWindow:(NSWindow *)presentingWindow useEphemeralSession:(BOOL)useEphemeralSession { - if (useEphemeralSession) { - return [[OIDExternalUserAgentMacNoSSO alloc] initWithPresentingWindow:presentingWindow]; - } - return [[OIDExternalUserAgentMac alloc] initWithPresentingWindow:presentingWindow]; +- (id) + userAgentWithPresentingWindow:(NSWindow *)presentingWindow + useEphemeralSession:(BOOL)useEphemeralSession { + if (useEphemeralSession) { + return [[OIDExternalUserAgentMacNoSSO alloc] + initWithPresentingWindow:presentingWindow]; + } + return [[OIDExternalUserAgentMac alloc] + initWithPresentingWindow:presentingWindow]; } @end diff --git a/flutter_appauth/macos/Classes/FlutterAppAuth.m b/flutter_appauth/macos/Classes/FlutterAppAuth.m deleted file mode 120000 index 0d1e69b4..00000000 --- a/flutter_appauth/macos/Classes/FlutterAppAuth.m +++ /dev/null @@ -1 +0,0 @@ -../../ios/Classes/FlutterAppAuth.m \ No newline at end of file diff --git a/flutter_appauth/macos/Classes/FlutterAppAuth.m b/flutter_appauth/macos/Classes/FlutterAppAuth.m new file mode 100644 index 00000000..be9477c8 --- /dev/null +++ b/flutter_appauth/macos/Classes/FlutterAppAuth.m @@ -0,0 +1,91 @@ +#import "FlutterAppAuth.h" + +@implementation FlutterAppAuth + ++ (NSMutableDictionary *)processResponses:(OIDTokenResponse *)tokenResponse + authResponse: + (OIDAuthorizationResponse *)authResponse { + NSMutableDictionary *processedResponses = [[NSMutableDictionary alloc] init]; + if (tokenResponse.accessToken) { + [processedResponses setValue:tokenResponse.accessToken + forKey:@"accessToken"]; + } + if (tokenResponse.accessTokenExpirationDate) { + [processedResponses + setValue:[[NSNumber alloc] + initWithDouble:[tokenResponse.accessTokenExpirationDate + timeIntervalSince1970] * + 1000] + forKey:@"accessTokenExpirationTime"]; + } + if (authResponse) { + if (authResponse.additionalParameters) { + [processedResponses setObject:authResponse.additionalParameters + forKey:@"authorizationAdditionalParameters"]; + } + if (authResponse.request && authResponse.request.nonce) { + [processedResponses setObject:authResponse.request.nonce forKey:@"nonce"]; + } + } + if (tokenResponse.additionalParameters) { + [processedResponses setObject:tokenResponse.additionalParameters + forKey:@"tokenAdditionalParameters"]; + } + if (tokenResponse.idToken) { + [processedResponses setValue:tokenResponse.idToken forKey:@"idToken"]; + } + if (tokenResponse.refreshToken) { + [processedResponses setValue:tokenResponse.refreshToken + forKey:@"refreshToken"]; + } + if (tokenResponse.tokenType) { + [processedResponses setValue:tokenResponse.tokenType forKey:@"tokenType"]; + } + if (tokenResponse.scope) { + [processedResponses + setObject:[tokenResponse.scope componentsSeparatedByString:@" "] + forKey:@"scopes"]; + } + + return processedResponses; +} + ++ (void)finishWithError:(NSString *)errorCode + message:(NSString *)message + result:(FlutterResult)result { + result([FlutterError errorWithCode:errorCode message:message details:nil]); +} + ++ (NSString *)formatMessageWithError:(NSString *)messageFormat + error:(NSError *_Nullable)error { + NSString *formattedMessage = + [NSString stringWithFormat:messageFormat, [error localizedDescription]]; + return formattedMessage; +} + +@end + +@implementation AppAuthAuthorization + +- (id) + performAuthorization:(OIDServiceConfiguration *)serviceConfiguration + clientId:(NSString *)clientId + clientSecret:(NSString *)clientSecret + scopes:(NSArray *)scopes + redirectUrl:(NSString *)redirectUrl + additionalParameters:(NSDictionary *)additionalParameters + preferEphemeralSession:(BOOL)preferEphemeralSession + result:(FlutterResult)result + exchangeCode:(BOOL)exchangeCode + nonce:(NSString *)nonce { + return nil; +} + +- (id) + performEndSessionRequest:(OIDServiceConfiguration *)serviceConfiguration + requestParameters:(EndSessionRequestParameters *)requestParameters + result:(FlutterResult)result { + return nil; +} + +@end diff --git a/flutter_appauth/macos/Classes/FlutterAppauthPlugin.m b/flutter_appauth/macos/Classes/FlutterAppauthPlugin.m deleted file mode 120000 index 15348db3..00000000 --- a/flutter_appauth/macos/Classes/FlutterAppauthPlugin.m +++ /dev/null @@ -1 +0,0 @@ -../../ios/Classes/FlutterAppauthPlugin.m \ No newline at end of file diff --git a/flutter_appauth/macos/Classes/FlutterAppauthPlugin.m b/flutter_appauth/macos/Classes/FlutterAppauthPlugin.m new file mode 100644 index 00000000..3ab0eae8 --- /dev/null +++ b/flutter_appauth/macos/Classes/FlutterAppauthPlugin.m @@ -0,0 +1,511 @@ +#import + +#import "FlutterAppauthPlugin.h" + +@interface ArgumentProcessor : NSObject ++ (id _Nullable)processArgumentValue:(NSDictionary *)arguments + withKey:(NSString *)key; +@end + +@implementation ArgumentProcessor + ++ (id _Nullable)processArgumentValue:(NSDictionary *)arguments + withKey:(NSString *)key { + return [arguments objectForKey:key] != [NSNull null] ? arguments[key] : nil; +} + +@end + +@interface TokenRequestParameters : NSObject +@property(nonatomic, strong) NSString *clientId; +@property(nonatomic, strong) NSString *clientSecret; +@property(nonatomic, strong) NSString *issuer; +@property(nonatomic, strong) NSString *grantType; +@property(nonatomic, strong) NSString *discoveryUrl; +@property(nonatomic, strong) NSString *redirectUrl; +@property(nonatomic, strong) NSString *refreshToken; +@property(nonatomic, strong) NSString *codeVerifier; +@property(nonatomic, strong) NSString *nonce; +@property(nonatomic, strong) NSString *authorizationCode; +@property(nonatomic, strong) NSArray *scopes; +@property(nonatomic, strong) NSDictionary *serviceConfigurationParameters; +@property(nonatomic, strong) NSDictionary *additionalParameters; +@property(nonatomic, readwrite) BOOL preferEphemeralSession; + +@end + +@implementation TokenRequestParameters +- (void)processArguments:(NSDictionary *)arguments { + _clientId = [ArgumentProcessor processArgumentValue:arguments + withKey:@"clientId"]; + _clientSecret = [ArgumentProcessor processArgumentValue:arguments + withKey:@"clientSecret"]; + _issuer = [ArgumentProcessor processArgumentValue:arguments + withKey:@"issuer"]; + _discoveryUrl = [ArgumentProcessor processArgumentValue:arguments + withKey:@"discoveryUrl"]; + _redirectUrl = [ArgumentProcessor processArgumentValue:arguments + withKey:@"redirectUrl"]; + _refreshToken = [ArgumentProcessor processArgumentValue:arguments + withKey:@"refreshToken"]; + _nonce = [ArgumentProcessor processArgumentValue:arguments withKey:@"nonce"]; + _authorizationCode = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"authorizationCode"]; + _codeVerifier = [ArgumentProcessor processArgumentValue:arguments + withKey:@"codeVerifier"]; + _grantType = [ArgumentProcessor processArgumentValue:arguments + withKey:@"grantType"]; + _scopes = [ArgumentProcessor processArgumentValue:arguments + withKey:@"scopes"]; + _serviceConfigurationParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"serviceConfiguration"]; + _additionalParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"additionalParameters"]; + _preferEphemeralSession = [[ArgumentProcessor + processArgumentValue:arguments + withKey:@"preferEphemeralSession"] isEqual:@YES]; +} + +- (id)initWithArguments:(NSDictionary *)arguments { + [self processArguments:arguments]; + return self; +} + +@end + +@interface AuthorizationTokenRequestParameters : TokenRequestParameters +@property(nonatomic, strong) NSString *loginHint; +@property(nonatomic, strong) NSArray *promptValues; +@property(nonatomic, strong) NSString *responseMode; +@end + +@implementation AuthorizationTokenRequestParameters +- (id)initWithArguments:(NSDictionary *)arguments { + [super processArguments:arguments]; + _loginHint = [ArgumentProcessor processArgumentValue:arguments + withKey:@"loginHint"]; + _promptValues = [ArgumentProcessor processArgumentValue:arguments + withKey:@"promptValues"]; + _responseMode = [ArgumentProcessor processArgumentValue:arguments + withKey:@"responseMode"]; + return self; +} +@end + +@implementation EndSessionRequestParameters +- (id)initWithArguments:(NSDictionary *)arguments { + _idTokenHint = [ArgumentProcessor processArgumentValue:arguments + withKey:@"idTokenHint"]; + _postLogoutRedirectUrl = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"postLogoutRedirectUrl"]; + _state = [ArgumentProcessor processArgumentValue:arguments withKey:@"state"]; + _issuer = [ArgumentProcessor processArgumentValue:arguments + withKey:@"issuer"]; + _discoveryUrl = [ArgumentProcessor processArgumentValue:arguments + withKey:@"discoveryUrl"]; + _serviceConfigurationParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"serviceConfiguration"]; + _additionalParameters = + [ArgumentProcessor processArgumentValue:arguments + withKey:@"additionalParameters"]; + _preferEphemeralSession = [[ArgumentProcessor + processArgumentValue:arguments + withKey:@"preferEphemeralSession"] isEqual:@YES]; + return self; +} +@end + +@implementation FlutterAppauthPlugin + +FlutterMethodChannel *channel; +AppAuthAuthorization *authorization; + ++ (void)registerWithRegistrar:(NSObject *)registrar { + channel = [FlutterMethodChannel + methodChannelWithName:@"crossingthestreams.io/flutter_appauth" + binaryMessenger:[registrar messenger]]; + FlutterAppauthPlugin *instance = [[FlutterAppauthPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; + +#if TARGET_OS_OSX + authorization = [[AppAuthMacOSAuthorization alloc] init]; + + NSAppleEventManager *appleEventManager = + [NSAppleEventManager sharedAppleEventManager]; + [appleEventManager setEventHandler:instance + andSelector:@selector(handleGetURLEvent: + withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; +#else + authorization = [[AppAuthIOSAuthorization alloc] init]; + + [registrar addApplicationDelegate:instance]; +#endif +} + +- (void)handleMethodCall:(FlutterMethodCall *)call + result:(FlutterResult)result { + if ([AUTHORIZE_AND_EXCHANGE_CODE_METHOD isEqualToString:call.method]) { + [self handleAuthorizeMethodCall:[call arguments] + result:result + exchangeCode:true]; + } else if ([AUTHORIZE_METHOD isEqualToString:call.method]) { + [self handleAuthorizeMethodCall:[call arguments] + result:result + exchangeCode:false]; + } else if ([TOKEN_METHOD isEqualToString:call.method]) { + [self handleTokenMethodCall:[call arguments] result:result]; + } else if ([END_SESSION_METHOD isEqualToString:call.method]) { + [self handleEndSessionMethodCall:[call arguments] result:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)ensureAdditionalParametersInitialized: + (AuthorizationTokenRequestParameters *)requestParameters { + if (!requestParameters.additionalParameters) { + requestParameters.additionalParameters = [[NSMutableDictionary alloc] init]; + } +} + +- (void)handleAuthorizeMethodCall:(NSDictionary *)arguments + result:(FlutterResult)result + exchangeCode:(BOOL)exchangeCode { + AuthorizationTokenRequestParameters *requestParameters = + [[AuthorizationTokenRequestParameters alloc] initWithArguments:arguments]; + [self ensureAdditionalParametersInitialized:requestParameters]; + if (requestParameters.loginHint) { + [requestParameters.additionalParameters setValue:requestParameters.loginHint + forKey:@"login_hint"]; + } + if (requestParameters.promptValues) { + [requestParameters.additionalParameters + setValue:[requestParameters.promptValues componentsJoinedByString:@" "] + forKey:@"prompt"]; + } + if (requestParameters.responseMode) { + [requestParameters.additionalParameters + setValue:requestParameters.responseMode + forKey:@"response_mode"]; + } + + if (requestParameters.serviceConfigurationParameters != nil) { + OIDServiceConfiguration *serviceConfiguration = + [self processServiceConfigurationParameters: + requestParameters.serviceConfigurationParameters]; + _currentAuthorizationFlow = [authorization + performAuthorization:serviceConfiguration + clientId:requestParameters.clientId + clientSecret:requestParameters.clientSecret + scopes:requestParameters.scopes + redirectUrl:requestParameters.redirectUrl + additionalParameters:requestParameters.additionalParameters + preferEphemeralSession:requestParameters.preferEphemeralSession + result:result + exchangeCode:exchangeCode + nonce:requestParameters.nonce]; + } else if (requestParameters.discoveryUrl) { + NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; + [OIDAuthorizationService + discoverServiceConfigurationForDiscoveryURL:discoveryUrl + completion:^( + OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self + finishWithDiscoveryError:error + result: + result]; + return; + } + + self->_currentAuthorizationFlow = [authorization + performAuthorization: + configuration + clientId: + requestParameters + .clientId + clientSecret: + requestParameters + .clientSecret + scopes: + requestParameters + .scopes + redirectUrl: + requestParameters + .redirectUrl + additionalParameters: + requestParameters + .additionalParameters + preferEphemeralSession: + requestParameters + .preferEphemeralSession + result:result + exchangeCode: + exchangeCode + nonce: + requestParameters + .nonce]; + }]; + } else { + NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; + [OIDAuthorizationService + discoverServiceConfigurationForIssuer:issuerUrl + completion:^(OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self finishWithDiscoveryError:error + result:result]; + return; + } + + self->_currentAuthorizationFlow = [authorization + performAuthorization:configuration + clientId: + requestParameters + .clientId + clientSecret: + requestParameters + .clientSecret + scopes: + requestParameters + .scopes + redirectUrl: + requestParameters + .redirectUrl + additionalParameters: + requestParameters + .additionalParameters + preferEphemeralSession: + requestParameters + .preferEphemeralSession + result:result + exchangeCode:exchangeCode + nonce: + requestParameters + .nonce]; + }]; + } +} + +- (void)finishWithDiscoveryError:(NSError *_Nullable)error + result:(FlutterResult)result { + NSString *message = [NSString stringWithFormat:DISCOVERY_ERROR_MESSAGE_FORMAT, + [error localizedDescription]]; + [FlutterAppAuth finishWithError:DISCOVERY_ERROR_CODE + message:message + result:result]; +} + +- (OIDServiceConfiguration *)processServiceConfigurationParameters: + (NSDictionary *)serviceConfigurationParameters { + NSURL *endSessionEndpoint = + serviceConfigurationParameters[@"endSessionEndpoint"] == [NSNull null] + ? nil + : [NSURL URLWithString:serviceConfigurationParameters + [@"endSessionEndpoint"]]; + OIDServiceConfiguration *serviceConfiguration = + [[OIDServiceConfiguration alloc] + initWithAuthorizationEndpoint: + [NSURL URLWithString:serviceConfigurationParameters + [@"authorizationEndpoint"]] + tokenEndpoint: + [NSURL + URLWithString:serviceConfigurationParameters + [@"tokenEndpoint"]] + issuer:nil + registrationEndpoint:nil + endSessionEndpoint:endSessionEndpoint]; + return serviceConfiguration; +} + +- (void)handleTokenMethodCall:(NSDictionary *)arguments + result:(FlutterResult)result { + TokenRequestParameters *requestParameters = + [[TokenRequestParameters alloc] initWithArguments:arguments]; + if (requestParameters.serviceConfigurationParameters != nil) { + OIDServiceConfiguration *serviceConfiguration = + [self processServiceConfigurationParameters: + requestParameters.serviceConfigurationParameters]; + [self performTokenRequest:serviceConfiguration + requestParameters:requestParameters + result:result]; + } else if (requestParameters.discoveryUrl) { + NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; + + [OIDAuthorizationService + discoverServiceConfigurationForDiscoveryURL:discoveryUrl + completion:^( + OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self + finishWithDiscoveryError:error + result: + result]; + return; + } + + [self + performTokenRequest:configuration + requestParameters: + requestParameters + result:result]; + }]; + } else { + NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; + [OIDAuthorizationService + discoverServiceConfigurationForIssuer:issuerUrl + completion:^(OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self finishWithDiscoveryError:error + result:result]; + return; + } + + [self performTokenRequest:configuration + requestParameters:requestParameters + result:result]; + }]; + } +} + +- (void)handleEndSessionMethodCall:(NSDictionary *)arguments + result:(FlutterResult)result { + EndSessionRequestParameters *requestParameters = + [[EndSessionRequestParameters alloc] initWithArguments:arguments]; + if (requestParameters.serviceConfigurationParameters != nil) { + OIDServiceConfiguration *serviceConfiguration = + [self processServiceConfigurationParameters: + requestParameters.serviceConfigurationParameters]; + _currentAuthorizationFlow = + [authorization performEndSessionRequest:serviceConfiguration + requestParameters:requestParameters + result:result]; + } else if (requestParameters.discoveryUrl) { + NSURL *discoveryUrl = [NSURL URLWithString:requestParameters.discoveryUrl]; + + [OIDAuthorizationService + discoverServiceConfigurationForDiscoveryURL:discoveryUrl + completion:^( + OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self + finishWithDiscoveryError:error + result: + result]; + return; + } + + self->_currentAuthorizationFlow = + [authorization + performEndSessionRequest: + configuration + requestParameters: + requestParameters + result: + result]; + }]; + } else { + NSURL *issuerUrl = [NSURL URLWithString:requestParameters.issuer]; + [OIDAuthorizationService + discoverServiceConfigurationForIssuer:issuerUrl + completion:^(OIDServiceConfiguration + *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + [self finishWithDiscoveryError:error + result:result]; + return; + } + + self->_currentAuthorizationFlow = + [authorization + performEndSessionRequest: + configuration + requestParameters: + requestParameters + result:result]; + }]; + } +} + +- (void)performTokenRequest:(OIDServiceConfiguration *)serviceConfiguration + requestParameters:(TokenRequestParameters *)requestParameters + result:(FlutterResult)result { + OIDTokenRequest *tokenRequest = [[OIDTokenRequest alloc] + initWithConfiguration:serviceConfiguration + grantType:requestParameters.grantType + authorizationCode:requestParameters.authorizationCode + redirectURL:[NSURL URLWithString:requestParameters.redirectUrl] + clientID:requestParameters.clientId + clientSecret:requestParameters.clientSecret + scopes:requestParameters.scopes + refreshToken:requestParameters.refreshToken + codeVerifier:requestParameters.codeVerifier + additionalParameters:requestParameters.additionalParameters]; + [OIDAuthorizationService + performTokenRequest:tokenRequest + callback:^(OIDTokenResponse *_Nullable response, + NSError *_Nullable error) { + if (response) { + result([FlutterAppAuth processResponses:response + authResponse:nil]); + } else { + NSString *message = [NSString + stringWithFormat:TOKEN_ERROR_MESSAGE_FORMAT, + [error localizedDescription]]; + [FlutterAppAuth finishWithError:TOKEN_ERROR_CODE + message:message + result:result]; + } + }]; +} + +#if TARGET_OS_IOS +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options: + (NSDictionary *)options { + if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { + _currentAuthorizationFlow = nil; + return YES; + } + + return NO; +} + +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + return [self application:application openURL:url options:@{}]; +} +#endif + +#if TARGET_OS_OSX +- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event + withReplyEvent:(NSAppleEventDescriptor *)replyEvent { + NSString *URLString = + [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + NSURL *URL = [NSURL URLWithString:URLString]; + [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL]; + _currentAuthorizationFlow = nil; +} +#endif + +@end diff --git a/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.h b/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.h index 4a17eb47..22f1e1ba 100644 --- a/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.h +++ b/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.h @@ -1,7 +1,9 @@ /*! @file OIDExternalUserAgentMacNoSSO.h - @brief OIDExternalUserAgentMacNoSSO is custom user agent based on the default user agent in the AppAuth iOS SDK found here: + @brief OIDExternalUserAgentMacNoSSO is custom user agent based on the + default user agent in the AppAuth iOS SDK found here: https://github.com/openid/AppAuth-iOS/blob/master/Source/AppAuth/macOS/OIDExternalUserAgentMac.h - Ths user agent allows setting `prefersEphemeralSession` flag on macOS 10.15 or newer to avoid cookies being shared across the device + Ths user agent allows setting `prefersEphemeralSession` flag on + macOS 10.15 or newer to avoid cookies being shared across the device @copydetails Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,22 +18,25 @@ limitations under the License. */ -#import #import +#import NS_ASSUME_NONNULL_BEGIN -/*! @brief A Mac-specific external user-agent UI Coordinator that uses the default browser to - present an external user-agent request. +/*! @brief A Mac-specific external user-agent UI Coordinator that uses the + default browser to present an external user-agent request. */ @interface OIDExternalUserAgentMacNoSSO : NSObject /*! @brief The designated initializer. - @param presentingWindow The window from which to present the ASWebAuthenticationSession. + @param presentingWindow The window from which to present the + ASWebAuthenticationSession. */ -- (instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithPresentingWindow:(NSWindow *)presentingWindow + NS_DESIGNATED_INITIALIZER; -- (instancetype)init __deprecated_msg("Use initWithPresentingWindow for macOS 10.15 and above."); +- (instancetype)init __deprecated_msg( + "Use initWithPresentingWindow for macOS 10.15 and above."); @end diff --git a/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.m b/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.m index 5a8a1cd6..9af763cb 100644 --- a/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.m +++ b/flutter_appauth/macos/Classes/OIDExternalUserAgentMacNoSSO.m @@ -1,7 +1,9 @@ /*! @file OIDExternalUserAgentMacNoSSO.m - @brief OIDExternalUserAgentMacNoSSO is custom user agent based on the default user agent in the AppAuth iOS SDK found here: + @brief OIDExternalUserAgentMacNoSSO is custom user agent based on the + default user agent in the AppAuth iOS SDK found here: https://github.com/openid/AppAuth-iOS/blob/master/Source/AppAuth/macOS/OIDExternalUserAgentMac.m - Ths user agent allows setting `prefersEphemeralSession` flag on macOS 10.15 or newer to avoid cookies being shared across the device + Ths user agent allows setting `prefersEphemeralSession` flag on + macOS 10.15 or newer to avoid cookies being shared across the device @copydetails Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,16 +18,15 @@ limitations under the License. */ - #import "OIDExternalUserAgentMacNoSSO.h" -#import #import - +#import NS_ASSUME_NONNULL_BEGIN -@interface OIDExternalUserAgentMacNoSSO () +@interface OIDExternalUserAgentMacNoSSO () < + ASWebAuthenticationPresentationContextProviding> @end @implementation OIDExternalUserAgentMacNoSSO { @@ -55,7 +56,8 @@ - (instancetype)init { } - (BOOL)presentExternalUserAgentRequest:(id)request - session:(id)session { + session: + (id)session { if (_externalUserAgentFlowInProgress) { // TODO: Handle errors as authorization is already in progress. return NO; @@ -70,25 +72,28 @@ - (BOOL)presentExternalUserAgentRequest:(id)request __weak OIDExternalUserAgentMacNoSSO *weakSelf = self; NSString *redirectScheme = request.redirectScheme; ASWebAuthenticationSession *authenticationSession = - [[ASWebAuthenticationSession alloc] initWithURL:requestURL - callbackURLScheme:redirectScheme - completionHandler:^(NSURL * _Nullable callbackURL, - NSError * _Nullable error) { - __strong OIDExternalUserAgentMacNoSSO *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - strongSelf->_webAuthenticationSession = nil; - if (callbackURL) { - [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; - } else { - NSError *safariError = - [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow - underlyingError:error - description:nil]; - [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; - } - }]; + [[ASWebAuthenticationSession alloc] + initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL *_Nullable callbackURL, + NSError *_Nullable error) { + __strong OIDExternalUserAgentMacNoSSO *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationSession = nil; + if (callbackURL) { + [strongSelf->_session + resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session + failExternalUserAgentFlowWithError:safariError]; + } + }]; authenticationSession.presentationContextProvider = self; @@ -101,33 +106,38 @@ - (BOOL)presentExternalUserAgentRequest:(id)request BOOL openedBrowser = [[NSWorkspace sharedWorkspace] openURL:requestURL]; if (!openedBrowser) { [self cleanUp]; - NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeBrowserOpenError - underlyingError:nil - description:@"Unable to open the browser."]; + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeBrowserOpenError + underlyingError:nil + description:@"Unable to open the browser."]; [session failExternalUserAgentFlowWithError:safariError]; } return openedBrowser; } -- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { +- (void)dismissExternalUserAgentAnimated:(BOOL)animated + completion:(void (^)(void))completion { if (!_externalUserAgentFlowInProgress) { // Ignore this call if there is no authorization flow in progress. - if (completion) completion(); + if (completion) + completion(); return; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" - ASWebAuthenticationSession *webAuthenticationSession = _webAuthenticationSession; + ASWebAuthenticationSession *webAuthenticationSession = + _webAuthenticationSession; #pragma clang diagnostic pop - // Ideally the browser tab with the URL should be closed here, but the AppAuth library does not - // control the browser. + // Ideally the browser tab with the URL should be closed here, but the AppAuth + // library does not control the browser. [self cleanUp]; if (webAuthenticationSession) { // dismiss the ASWebAuthenticationSession [webAuthenticationSession cancel]; - if (completion) completion(); + if (completion) + completion(); } else if (completion) { completion(); } @@ -141,8 +151,8 @@ - (void)cleanUp { #pragma mark - ASWebAuthenticationPresentationContextProviding -- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session - API_AVAILABLE(macosx(10.15)) { +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession: + (ASWebAuthenticationSession *)session API_AVAILABLE(macosx(10.15)) { return _presentingWindow; } diff --git a/flutter_appauth_platform_interface/test/method_channel_flutter_appauth_test.dart b/flutter_appauth_platform_interface/test/method_channel_flutter_appauth_test.dart index 1981db55..748fecb2 100644 --- a/flutter_appauth_platform_interface/test/method_channel_flutter_appauth_test.dart +++ b/flutter_appauth_platform_interface/test/method_channel_flutter_appauth_test.dart @@ -8,8 +8,10 @@ void main() { const MethodChannel channel = MethodChannel('crossingthestreams.io/flutter_appauth'); final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); + return null; }); tearDown(() {