From 255fad46b51881d4b8c4a07fec55153993466f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Mon, 19 Feb 2018 19:55:24 +0000 Subject: [PATCH 01/10] Allow insecure requests on Android (@TODO: refactor) --- .../com/reactlibrary/RNAppAuthModule.java | 69 ++++++++++++++++--- index.js | 16 +++-- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/com/reactlibrary/RNAppAuthModule.java b/android/src/main/java/com/reactlibrary/RNAppAuthModule.java index 5eee9a5c..f0155d14 100644 --- a/android/src/main/java/com/reactlibrary/RNAppAuthModule.java +++ b/android/src/main/java/com/reactlibrary/RNAppAuthModule.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.facebook.react.bridge.ActivityEventListener; @@ -22,14 +23,23 @@ import net.openid.appauth.AuthorizationResponse; import net.openid.appauth.AuthorizationService; import net.openid.appauth.AuthorizationServiceConfiguration; +import net.openid.appauth.Preconditions; import net.openid.appauth.ResponseTypeValues; import net.openid.appauth.TokenResponse; import net.openid.appauth.TokenRequest; - +import net.openid.appauth.connectivity.ConnectionBuilder; +import net.openid.appauth.connectivity.DefaultConnectionBuilder; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Connection; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.concurrent.TimeUnit; public class RNAppAuthModule extends ReactContextBaseJavaModule implements ActivityEventListener { @@ -96,6 +106,22 @@ private HashMap additionalParametersToMap(ReadableMap additional return additionalParametersHash; } + private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) { + + if (allowInsecureConnections.equals(true)) { + return new UnsafeConnectionBuilder(); + } + + return DefaultConnectionBuilder.INSTANCE; + } + + static Uri buildConfigurationUriFromIssuer(Uri openIdConnectIssuerUri) { + return openIdConnectIssuerUri.buildUpon() + .appendPath(AuthorizationServiceConfiguration.WELL_KNOWN_PATH) + .appendPath(AuthorizationServiceConfiguration.OPENID_CONFIGURATION_RESOURCE) + .build(); + } + @ReactMethod public void authorize( String issuer, @@ -103,6 +129,7 @@ public void authorize( final String clientId, final ReadableArray scopes, final ReadableMap additionalParameters, + final Boolean dangerouslyAllowInsecureHttpRequests, final Promise promise ) { @@ -111,9 +138,11 @@ public void authorize( final Activity currentActivity = getCurrentActivity(); final String scopesString = this.arrayToString(scopes); + final Uri issuerUri = Uri.parse(issuer); + final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests); - AuthorizationServiceConfiguration.fetchFromIssuer( - Uri.parse(issuer), + AuthorizationServiceConfiguration.fetchFromUrl( + buildConfigurationUriFromIssuer(issuerUri), new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { public void onFetchConfigurationCompleted( @Nullable AuthorizationServiceConfiguration serviceConfiguration, @@ -143,7 +172,9 @@ public void onFetchConfigurationCompleted( currentActivity.startActivityForResult(authIntent, 0); } - }); + }, + builder + ); } @@ -155,14 +186,16 @@ public void refresh( final String refreshToken, final ReadableArray scopes, final ReadableMap additionalParameters, + final Boolean dangerouslyAllowInsecureHttpRequests, final Promise promise ) { final Context context = this.reactContext; - final String scopesString = this.arrayToString(scopes); + final Uri issuerUri = Uri.parse(issuer); + final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests); - AuthorizationServiceConfiguration.fetchFromIssuer( - Uri.parse(issuer), + AuthorizationServiceConfiguration.fetchFromUrl( + buildConfigurationUriFromIssuer(issuerUri), new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { public void onFetchConfigurationCompleted( @Nullable AuthorizationServiceConfiguration serviceConfiguration, @@ -203,7 +236,8 @@ public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable }); } - }); + }, + builder); } @Override @@ -249,3 +283,22 @@ public String getName() { return "RNAppAuth"; } } + + +final class UnsafeConnectionBuilder implements ConnectionBuilder { + + private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15); + private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10); + + + @NonNull + @Override + public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException { + Preconditions.checkNotNull(uri, "url must not be null"); + HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection(); + conn.setConnectTimeout(CONNECTION_TIMEOUT_MS); + conn.setReadTimeout(READ_TIMEOUT_MS); + conn.setInstanceFollowRedirects(false); + return conn; + } +} \ No newline at end of file diff --git a/index.js b/index.js index f1406936..3e59ae62 100644 --- a/index.js +++ b/index.js @@ -12,18 +12,25 @@ const validateClientId = clientId => const validateRedirectUrl = redirectUrl => invariant(typeof redirectUrl === 'string', 'Config error: redirectUrl must be a string'); -export const authorize = ({ issuer, redirectUrl, clientId, scopes, additionalParameters }) => { +export const authorize = ({ issuer, redirectUrl, clientId, scopes, additionalParameters, dangerouslyAllowInsecureHttpRequests = false }) => { validateScopes(scopes); validateIssuer(issuer); validateClientId(clientId); validateRedirectUrl(redirectUrl); // TODO: validateAdditionalParameters - return RNAppAuth.authorize(issuer, redirectUrl, clientId, scopes, additionalParameters); + return RNAppAuth.authorize( + issuer, + redirectUrl, + clientId, + scopes, + additionalParameters, + dangerouslyAllowInsecureHttpRequests + ); }; export const refresh = ( - { issuer, redirectUrl, clientId, scopes, additionalParameters }, + { issuer, redirectUrl, clientId, scopes, additionalParameters, dangerouslyAllowInsecureHttpRequests = false }, { refreshToken } ) => { validateScopes(scopes); @@ -39,7 +46,8 @@ export const refresh = ( clientId, refreshToken, scopes, - additionalParameters + additionalParameters, + dangerouslyAllowInsecureHttpRequests ); }; From 9d82377f238251ab2d1526eb50cc328e33731602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 12:07:07 +0000 Subject: [PATCH 02/10] Pass through connection builder to token requests --- .../ConnectionBuilderForTesting.java | 128 ++++++++++++++++++ .../com/reactlibrary/RNAppAuthModule.java | 51 +++---- 2 files changed, 155 insertions(+), 24 deletions(-) create mode 100644 android/src/main/java/com/reactlibrary/ConnectionBuilderForTesting.java diff --git a/android/src/main/java/com/reactlibrary/ConnectionBuilderForTesting.java b/android/src/main/java/com/reactlibrary/ConnectionBuilderForTesting.java new file mode 100644 index 00000000..c045b33a --- /dev/null +++ b/android/src/main/java/com/reactlibrary/ConnectionBuilderForTesting.java @@ -0,0 +1,128 @@ +package com.reactlibrary; + +/* + * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import android.annotation.SuppressLint; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import net.openid.appauth.Preconditions; +import net.openid.appauth.connectivity.ConnectionBuilder; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * An implementation of {@link ConnectionBuilder} that permits connecting to http + * links, and ignores certificates for https connections. *THIS SHOULD NOT BE USED IN PRODUCTION + * CODE*. It is intended to facilitate easier testing of AppAuth against development servers + * only. + */ +public final class ConnectionBuilderForTesting implements ConnectionBuilder { + + public static final ConnectionBuilderForTesting INSTANCE = new ConnectionBuilderForTesting(); + + private static final String TAG = "ConnBuilder"; + + private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15); + private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10); + + private static final String HTTP = "http"; + private static final String HTTPS = "https"; + + @SuppressLint("TrustAllX509TrustManager") + private static final TrustManager[] ANY_CERT_MANAGER = new TrustManager[] { + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } + }; + + @SuppressLint("BadHostnameVerifier") + private static final HostnameVerifier ANY_HOSTNAME_VERIFIER = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + @Nullable + private static final SSLContext TRUSTING_CONTEXT; + + static { + SSLContext context; + try { + context = SSLContext.getInstance("SSL"); + } catch (NoSuchAlgorithmException e) { + Log.e("ConnBuilder", "Unable to acquire SSL context"); + context = null; + } + + SSLContext initializedContext = null; + if (context != null) { + try { + context.init(null, ANY_CERT_MANAGER, new java.security.SecureRandom()); + initializedContext = context; + } catch (KeyManagementException e) { + Log.e(TAG, "Failed to initialize trusting SSL context"); + } + } + + TRUSTING_CONTEXT = initializedContext; + } + + private ConnectionBuilderForTesting() { + // no need to construct new instances + } + + @NonNull + @Override + public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException { + Preconditions.checkNotNull(uri, "url must not be null"); + Preconditions.checkArgument(HTTP.equals(uri.getScheme()) || HTTPS.equals(uri.getScheme()), + "scheme or uri must be http or https"); + HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection(); + conn.setConnectTimeout(CONNECTION_TIMEOUT_MS); + conn.setReadTimeout(READ_TIMEOUT_MS); + conn.setInstanceFollowRedirects(false); + + if (conn instanceof HttpsURLConnection && TRUSTING_CONTEXT != null) { + HttpsURLConnection httpsConn = (HttpsURLConnection) conn; + httpsConn.setSSLSocketFactory(TRUSTING_CONTEXT.getSocketFactory()); + httpsConn.setHostnameVerifier(ANY_HOSTNAME_VERIFIER); + } + + return conn; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/RNAppAuthModule.java b/android/src/main/java/com/reactlibrary/RNAppAuthModule.java index f0155d14..c57a9dc6 100644 --- a/android/src/main/java/com/reactlibrary/RNAppAuthModule.java +++ b/android/src/main/java/com/reactlibrary/RNAppAuthModule.java @@ -18,6 +18,7 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.WritableMap; +import net.openid.appauth.AppAuthConfiguration; import net.openid.appauth.AuthorizationException; import net.openid.appauth.AuthorizationRequest; import net.openid.appauth.AuthorizationResponse; @@ -45,6 +46,7 @@ public class RNAppAuthModule extends ReactContextBaseJavaModule implements Activ private final ReactApplicationContext reactContext; private Promise promise; + private Boolean dangerouslyAllowInsecureHttpRequests; public RNAppAuthModule(ReactApplicationContext reactContext) { super(reactContext); @@ -109,7 +111,7 @@ private HashMap additionalParametersToMap(ReadableMap additional private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) { if (allowInsecureConnections.equals(true)) { - return new UnsafeConnectionBuilder(); + return ConnectionBuilderForTesting.INSTANCE; } return DefaultConnectionBuilder.INSTANCE; @@ -135,6 +137,7 @@ public void authorize( final Context context = this.reactContext; this.promise = promise; + this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests; final Activity currentActivity = getCurrentActivity(); final String scopesString = this.arrayToString(scopes); @@ -152,6 +155,7 @@ public void onFetchConfigurationCompleted( return; } + AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder( serviceConfiguration, @@ -165,9 +169,14 @@ public void onFetchConfigurationCompleted( authRequestBuilder.setAdditionalParameters(additionalParametersToMap(additionalParameters)); } - AuthorizationRequest authRequest = authRequestBuilder.build(); + AppAuthConfiguration configuration = + new AppAuthConfiguration + .Builder() + .setConnectionBuilder(builder) + .build(); - AuthorizationService authService = new AuthorizationService(context); + AuthorizationRequest authRequest = authRequestBuilder.build(); + AuthorizationService authService = new AuthorizationService(context, configuration); Intent authIntent = authService.getAuthorizationRequestIntent(authRequest); currentActivity.startActivityForResult(authIntent, 0); @@ -194,6 +203,8 @@ public void refresh( final Uri issuerUri = Uri.parse(issuer); final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests); + this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests; + AuthorizationServiceConfiguration.fetchFromUrl( buildConfigurationUriFromIssuer(issuerUri), new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() { @@ -221,7 +232,12 @@ public void onFetchConfigurationCompleted( TokenRequest tokenRequest = tokenRequestBuilder.build(); - AuthorizationService authService = new AuthorizationService(context); + final AppAuthConfiguration configuration = + new AppAuthConfiguration + .Builder() + .setConnectionBuilder(createConnectionBuilder(dangerouslyAllowInsecureHttpRequests)) + .build(); + AuthorizationService authService = new AuthorizationService(context, configuration); authService.performTokenRequest(tokenRequest, new AuthorizationService.TokenResponseCallback() { @Override @@ -252,7 +268,13 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, final Promise authorizePromise = this.promise; - AuthorizationService authService = new AuthorizationService(this.reactContext); + final AppAuthConfiguration configuration = + new AppAuthConfiguration + .Builder() + .setConnectionBuilder(createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests)) + .build(); + + AuthorizationService authService = new AuthorizationService(this.reactContext, configuration); authService.performTokenRequest( response.createTokenExchangeRequest(), @@ -282,23 +304,4 @@ public void onNewIntent(Intent intent) { public String getName() { return "RNAppAuth"; } -} - - -final class UnsafeConnectionBuilder implements ConnectionBuilder { - - private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15); - private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10); - - - @NonNull - @Override - public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException { - Preconditions.checkNotNull(uri, "url must not be null"); - HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection(); - conn.setConnectTimeout(CONNECTION_TIMEOUT_MS); - conn.setReadTimeout(READ_TIMEOUT_MS); - conn.setInstanceFollowRedirects(false); - return conn; - } } \ No newline at end of file From 6f8f49c20e8620ed1704bf120c23414e9d8d0cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 16:17:46 +0000 Subject: [PATCH 03/10] Refactor UnsafeConnectionBuilder functionality --- .../com/reactlibrary/RNAppAuthModule.java | 43 ++++++++----------- .../UnsafeConnectionBuilder.java} | 8 ++-- 2 files changed, 23 insertions(+), 28 deletions(-) rename android/src/main/java/com/reactlibrary/{ConnectionBuilderForTesting.java => utils/UnsafeConnectionBuilder.java} (94%) diff --git a/android/src/main/java/com/reactlibrary/RNAppAuthModule.java b/android/src/main/java/com/reactlibrary/RNAppAuthModule.java index c57a9dc6..771e815c 100644 --- a/android/src/main/java/com/reactlibrary/RNAppAuthModule.java +++ b/android/src/main/java/com/reactlibrary/RNAppAuthModule.java @@ -17,6 +17,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.WritableMap; +import com.reactlibrary.utils.UnsafeConnectionBuilder; import net.openid.appauth.AppAuthConfiguration; import net.openid.appauth.AuthorizationException; @@ -108,16 +109,22 @@ private HashMap additionalParametersToMap(ReadableMap additional return additionalParametersHash; } - private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) { + private AppAuthConfiguration createAppAuthConfiguration(ConnectionBuilder connectionBuilder) { + return new AppAuthConfiguration + .Builder() + .setConnectionBuilder(connectionBuilder) + .build(); + } + private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) { if (allowInsecureConnections.equals(true)) { - return ConnectionBuilderForTesting.INSTANCE; + return UnsafeConnectionBuilder.INSTANCE; } return DefaultConnectionBuilder.INSTANCE; } - static Uri buildConfigurationUriFromIssuer(Uri openIdConnectIssuerUri) { + private Uri buildConfigurationUriFromIssuer(Uri openIdConnectIssuerUri) { return openIdConnectIssuerUri.buildUpon() .appendPath(AuthorizationServiceConfiguration.WELL_KNOWN_PATH) .appendPath(AuthorizationServiceConfiguration.OPENID_CONFIGURATION_RESOURCE) @@ -136,13 +143,16 @@ public void authorize( ) { final Context context = this.reactContext; + + // store args in private fields for later use in onActivityResult handler this.promise = promise; this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests; - final Activity currentActivity = getCurrentActivity(); + final Activity currentActivity = getCurrentActivity(); final String scopesString = this.arrayToString(scopes); final Uri issuerUri = Uri.parse(issuer); final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests); + final AppAuthConfiguration configuration = this.createAppAuthConfiguration(builder); AuthorizationServiceConfiguration.fetchFromUrl( buildConfigurationUriFromIssuer(issuerUri), @@ -169,12 +179,6 @@ public void onFetchConfigurationCompleted( authRequestBuilder.setAdditionalParameters(additionalParametersToMap(additionalParameters)); } - AppAuthConfiguration configuration = - new AppAuthConfiguration - .Builder() - .setConnectionBuilder(builder) - .build(); - AuthorizationRequest authRequest = authRequestBuilder.build(); AuthorizationService authService = new AuthorizationService(context, configuration); Intent authIntent = authService.getAuthorizationRequestIntent(authRequest); @@ -202,7 +206,9 @@ public void refresh( final String scopesString = this.arrayToString(scopes); final Uri issuerUri = Uri.parse(issuer); final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests); + final AppAuthConfiguration configuration = createAppAuthConfiguration(builder); + // store setting in private field for later use in onActivityResult handler this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests; AuthorizationServiceConfiguration.fetchFromUrl( @@ -231,14 +237,7 @@ public void onFetchConfigurationCompleted( TokenRequest tokenRequest = tokenRequestBuilder.build(); - - final AppAuthConfiguration configuration = - new AppAuthConfiguration - .Builder() - .setConnectionBuilder(createConnectionBuilder(dangerouslyAllowInsecureHttpRequests)) - .build(); AuthorizationService authService = new AuthorizationService(context, configuration); - authService.performTokenRequest(tokenRequest, new AuthorizationService.TokenResponseCallback() { @Override public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable AuthorizationException ex) { @@ -267,15 +266,11 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, } final Promise authorizePromise = this.promise; - - final AppAuthConfiguration configuration = - new AppAuthConfiguration - .Builder() - .setConnectionBuilder(createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests)) - .build(); + final AppAuthConfiguration configuration = createAppAuthConfiguration( + createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests) + ); AuthorizationService authService = new AuthorizationService(this.reactContext, configuration); - authService.performTokenRequest( response.createTokenExchangeRequest(), new AuthorizationService.TokenResponseCallback() { diff --git a/android/src/main/java/com/reactlibrary/ConnectionBuilderForTesting.java b/android/src/main/java/com/reactlibrary/utils/UnsafeConnectionBuilder.java similarity index 94% rename from android/src/main/java/com/reactlibrary/ConnectionBuilderForTesting.java rename to android/src/main/java/com/reactlibrary/utils/UnsafeConnectionBuilder.java index c045b33a..10b192d0 100644 --- a/android/src/main/java/com/reactlibrary/ConnectionBuilderForTesting.java +++ b/android/src/main/java/com/reactlibrary/utils/UnsafeConnectionBuilder.java @@ -1,4 +1,4 @@ -package com.reactlibrary; +package com.reactlibrary.utils; /* * Copyright 2016 The AppAuth for Android Authors. All Rights Reserved. @@ -45,9 +45,9 @@ * CODE*. It is intended to facilitate easier testing of AppAuth against development servers * only. */ -public final class ConnectionBuilderForTesting implements ConnectionBuilder { +public final class UnsafeConnectionBuilder implements ConnectionBuilder { - public static final ConnectionBuilderForTesting INSTANCE = new ConnectionBuilderForTesting(); + public static final UnsafeConnectionBuilder INSTANCE = new UnsafeConnectionBuilder(); private static final String TAG = "ConnBuilder"; @@ -102,7 +102,7 @@ public boolean verify(String hostname, SSLSession session) { TRUSTING_CONTEXT = initializedContext; } - private ConnectionBuilderForTesting() { + private UnsafeConnectionBuilder() { // no need to construct new instances } From 25c1b1493133bcaaaa89627ca625a6f21e8350ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 16:33:50 +0000 Subject: [PATCH 04/10] Add ATS NSExceptionAllowsInsecureHTTPLoads to iOS Example app to enable testing new dangerouslyAllowInsecureHttpRequests flag --- Example/ios/AppAuthExample/Info.plist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Example/ios/AppAuthExample/Info.plist b/Example/ios/AppAuthExample/Info.plist index ae1dad08..753b660b 100644 --- a/Example/ios/AppAuthExample/Info.plist +++ b/Example/ios/AppAuthExample/Info.plist @@ -39,6 +39,11 @@ NSExceptionDomains + demo.identityserver.io + + NSExceptionAllowsInsecureHTTPLoads + + localhost NSExceptionAllowsInsecureHTTPLoads From c4c1d99be3343a2db62eeb97584812839d46692a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 16:34:24 +0000 Subject: [PATCH 05/10] Only pass dangerouslyAllowInsecureHttpRequests to authorize/refresh methods on Android --- index.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 3e59ae62..017de87f 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ import invariant from 'invariant'; -import { NativeModules } from 'react-native'; +import { NativeModules, Platform } from 'react-native'; const { RNAppAuth } = NativeModules; @@ -19,14 +19,18 @@ export const authorize = ({ issuer, redirectUrl, clientId, scopes, additionalPar validateRedirectUrl(redirectUrl); // TODO: validateAdditionalParameters - return RNAppAuth.authorize( + const nativeMethodArguments = [ issuer, redirectUrl, clientId, scopes, - additionalParameters, - dangerouslyAllowInsecureHttpRequests - ); + additionalParameters + ] + if (Platform.OS === 'android') { + nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests); + } + + return RNAppAuth.authorize(...nativeMethodArguments); }; export const refresh = ( @@ -40,15 +44,20 @@ export const refresh = ( invariant(refreshToken, 'Please pass in a refresh token'); // TODO: validateAdditionalParameters - return RNAppAuth.refresh( + const nativeMethodArguments = [ issuer, redirectUrl, clientId, refreshToken, scopes, - additionalParameters, - dangerouslyAllowInsecureHttpRequests - ); + additionalParameters + ]; + + if (Platform.OS === 'android') { + nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests); + } + + return RNAppAuth.refresh(...nativeMethodArguments); }; export const revoke = async ({ clientId, issuer }, { tokenToRevoke, sendClientId = false }) => { From a8095eead1f01c13b453274d1fa65bbb3c26bcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 16:45:14 +0000 Subject: [PATCH 06/10] Add dangerouslyAllowInsecureHttpRequests to typescript declaration --- index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/index.d.ts b/index.d.ts index 196708f9..57756774 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,6 +6,7 @@ export interface AuthConfiguration extends BaseAuthConfiguration { scopes: string[]; redirectUrl: string; additionalParameters?: { [name: string]: string }; + dangerouslyAllowInsecureHttpRequests?: boolean; } export interface RevokeConfiguration { From 0ec8e3d6b1436a2a3674c5acdc21824c9abc791c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 17:26:22 +0000 Subject: [PATCH 07/10] Add tests for dangerouslyAllowInsecureHttpRequests --- index.spec.js | 108 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/index.spec.js b/index.spec.js index d7af8f98..9cd7f840 100644 --- a/index.spec.js +++ b/index.spec.js @@ -7,6 +7,9 @@ jest.mock('react-native', () => ({ refresh: jest.fn(), }, }, + Platform: { + OS: 'ios', + }, })); describe('AppAuth', () => { @@ -65,7 +68,7 @@ describe('AppAuth', () => { }).toThrow('Scope error: please add at least one scope'); }); - it('calls the native wrapper with the correct args', () => { + it('calls the native wrapper with the correct args on iOS', () => { authorize(config); expect(mockAuthorize).toHaveBeenCalledWith( config.issuer, @@ -75,6 +78,52 @@ describe('AppAuth', () => { config.additionalParameters ); }); + + describe('Android-specific dangerouslyAllowInsecureHttpRequests parameter', () => { + beforeEach(() => { + require('react-native').Platform.OS = 'android'; + }); + + afterEach(() => { + require('react-native').Platform.OS = 'ios'; + }); + + it('calls the native wrapper with default value `false`', () => { + authorize(config); + expect(mockAuthorize).toHaveBeenCalledWith( + config.issuer, + config.redirectUrl, + config.clientId, + config.scopes, + config.additionalParameters, + false + ); + }); + + it('calls the native wrapper with passed value `false`', () => { + authorize({ ...config, dangerouslyAllowInsecureHttpRequests: false }); + expect(mockAuthorize).toHaveBeenCalledWith( + config.issuer, + config.redirectUrl, + config.clientId, + config.scopes, + config.additionalParameters, + false + ); + }); + + it('calls the native wrapper with passed value `true`', () => { + authorize({ ...config, dangerouslyAllowInsecureHttpRequests: true }); + expect(mockAuthorize).toHaveBeenCalledWith( + config.issuer, + config.redirectUrl, + config.clientId, + config.scopes, + config.additionalParameters, + true + ); + }); + }); }); describe('refresh', () => { @@ -119,7 +168,7 @@ describe('AppAuth', () => { }).toThrow('Scope error: please add at least one scope'); }); - it('calls the native wrapper with the correct args', () => { + it('calls the native wrapper with the correct args on iOS', () => { refresh({ ...config }, { refreshToken: 'such-token' }); expect(mockRefresh).toHaveBeenCalledWith( config.issuer, @@ -130,5 +179,60 @@ describe('AppAuth', () => { config.additionalParameters ); }); + + describe('Android-specific dangerouslyAllowInsecureHttpRequests parameter', () => { + beforeEach(() => { + require('react-native').Platform.OS = 'android'; + }); + + afterEach(() => { + require('react-native').Platform.OS = 'ios'; + }); + + it('calls the native wrapper with default value `false`', () => { + refresh(config, { refreshToken: 'such-token' }); + expect(mockRefresh).toHaveBeenCalledWith( + config.issuer, + config.redirectUrl, + config.clientId, + 'such-token', + config.scopes, + config.additionalParameters, + false + ); + }); + + it('calls the native wrapper with passed value `false`', () => { + refresh( + { ...config, dangerouslyAllowInsecureHttpRequests: false }, + { refreshToken: 'such-token' } + ); + expect(mockRefresh).toHaveBeenCalledWith( + config.issuer, + config.redirectUrl, + config.clientId, + 'such-token', + config.scopes, + config.additionalParameters, + false + ); + }); + + it('calls the native wrapper with passed value `true`', () => { + refresh( + { ...config, dangerouslyAllowInsecureHttpRequests: true }, + { refreshToken: 'such-token' } + ); + expect(mockRefresh).toHaveBeenCalledWith( + config.issuer, + config.redirectUrl, + config.clientId, + 'such-token', + config.scopes, + config.additionalParameters, + true + ); + }); + }); }); }); From 9ba11a9b60cfaf9a8e9debe24ccdcc412ad973d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 17:27:24 +0000 Subject: [PATCH 08/10] Run prettier --- index.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index 017de87f..d7ec5a87 100644 --- a/index.js +++ b/index.js @@ -12,20 +12,21 @@ const validateClientId = clientId => const validateRedirectUrl = redirectUrl => invariant(typeof redirectUrl === 'string', 'Config error: redirectUrl must be a string'); -export const authorize = ({ issuer, redirectUrl, clientId, scopes, additionalParameters, dangerouslyAllowInsecureHttpRequests = false }) => { +export const authorize = ({ + issuer, + redirectUrl, + clientId, + scopes, + additionalParameters, + dangerouslyAllowInsecureHttpRequests = false, +}) => { validateScopes(scopes); validateIssuer(issuer); validateClientId(clientId); validateRedirectUrl(redirectUrl); // TODO: validateAdditionalParameters - const nativeMethodArguments = [ - issuer, - redirectUrl, - clientId, - scopes, - additionalParameters - ] + const nativeMethodArguments = [issuer, redirectUrl, clientId, scopes, additionalParameters]; if (Platform.OS === 'android') { nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests); } @@ -34,7 +35,14 @@ export const authorize = ({ issuer, redirectUrl, clientId, scopes, additionalPar }; export const refresh = ( - { issuer, redirectUrl, clientId, scopes, additionalParameters, dangerouslyAllowInsecureHttpRequests = false }, + { + issuer, + redirectUrl, + clientId, + scopes, + additionalParameters, + dangerouslyAllowInsecureHttpRequests = false, + }, { refreshToken } ) => { validateScopes(scopes); @@ -50,7 +58,7 @@ export const refresh = ( clientId, refreshToken, scopes, - additionalParameters + additionalParameters, ]; if (Platform.OS === 'android') { From 22277ed82d31e34710f0de6a306f49494c0efb5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Tue, 20 Feb 2018 17:38:57 +0000 Subject: [PATCH 09/10] Add dangerouslyAllowInsecureHttpRequests to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fc2283f1..cbf579af 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ with optional overrides. * **additionalParameters** - (`object`) additional parameters that will be passed in the authorization request. Must be string values! E.g. setting `additionalParameters: { hello: 'world', foo: 'bar' }` would add `hello=world&foo=bar` to the authorization request. +* **dangerouslyAllowInsecureHttpRequests** - (`boolean`) _ANDROID_ whether to allow requests over plain HTTP or with self-signed SSL certificates. :warning: Can be useful for testing against local server, _should not be used in production._ #### result From b95a75868887b0041dc5689008ff58e09d74414f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jani=20Ev=C3=A4kallio?= Date: Wed, 21 Feb 2018 12:29:32 +0000 Subject: [PATCH 10/10] Add note about iOS ATS settings to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cbf579af..bf13ea89 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ with optional overrides. * **additionalParameters** - (`object`) additional parameters that will be passed in the authorization request. Must be string values! E.g. setting `additionalParameters: { hello: 'world', foo: 'bar' }` would add `hello=world&foo=bar` to the authorization request. -* **dangerouslyAllowInsecureHttpRequests** - (`boolean`) _ANDROID_ whether to allow requests over plain HTTP or with self-signed SSL certificates. :warning: Can be useful for testing against local server, _should not be used in production._ +* :warning: **dangerouslyAllowInsecureHttpRequests** - (`boolean`) _ANDROID_ whether to allow requests over plain HTTP or with self-signed SSL certificates. Can be useful for testing against local server, _should not be used in production._ This setting has no effect on iOS; to enable insecure HTTP requests, add a [NSExceptionAllowsInsecureHTTPLoads exception](https://cocoacasts.com/how-to-add-app-transport-security-exception-domains) to your App Transport Security settings. #### result