Skip to content

Commit

Permalink
Merge pull request #50 from FormidableLabs/feature/allow-insecure-req…
Browse files Browse the repository at this point in the history
…uests-v2

Allow insecure HTTP requests on Android
  • Loading branch information
jevakallio authored Feb 21, 2018
2 parents 898cf39 + b95a758 commit 2e17c94
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 26 deletions.
5 changes: 5 additions & 0 deletions Example/ios/AppAuthExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>demo.identityserver.io</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* :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

Expand Down
85 changes: 68 additions & 17 deletions android/src/main/java/com/reactlibrary/RNAppAuthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,25 +17,37 @@
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;
import net.openid.appauth.AuthorizationRequest;
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 {

private final ReactApplicationContext reactContext;
private Promise promise;
private Boolean dangerouslyAllowInsecureHttpRequests;

public RNAppAuthModule(ReactApplicationContext reactContext) {
super(reactContext);
Expand Down Expand Up @@ -96,24 +109,53 @@ private HashMap<String, String> additionalParametersToMap(ReadableMap additional
return additionalParametersHash;
}

private AppAuthConfiguration createAppAuthConfiguration(ConnectionBuilder connectionBuilder) {
return new AppAuthConfiguration
.Builder()
.setConnectionBuilder(connectionBuilder)
.build();
}

private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) {
if (allowInsecureConnections.equals(true)) {
return UnsafeConnectionBuilder.INSTANCE;
}

return DefaultConnectionBuilder.INSTANCE;
}

private Uri buildConfigurationUriFromIssuer(Uri openIdConnectIssuerUri) {
return openIdConnectIssuerUri.buildUpon()
.appendPath(AuthorizationServiceConfiguration.WELL_KNOWN_PATH)
.appendPath(AuthorizationServiceConfiguration.OPENID_CONFIGURATION_RESOURCE)
.build();
}

@ReactMethod
public void authorize(
String issuer,
final String redirectUrl,
final String clientId,
final ReadableArray scopes,
final ReadableMap additionalParameters,
final Boolean dangerouslyAllowInsecureHttpRequests,
final Promise promise
) {

final Context context = this.reactContext;

// store args in private fields for later use in onActivityResult handler
this.promise = promise;
final Activity currentActivity = getCurrentActivity();
this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;

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.fetchFromIssuer(
Uri.parse(issuer),
AuthorizationServiceConfiguration.fetchFromUrl(
buildConfigurationUriFromIssuer(issuerUri),
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
public void onFetchConfigurationCompleted(
@Nullable AuthorizationServiceConfiguration serviceConfiguration,
Expand All @@ -123,6 +165,7 @@ public void onFetchConfigurationCompleted(
return;
}


AuthorizationRequest.Builder authRequestBuilder =
new AuthorizationRequest.Builder(
serviceConfiguration,
Expand All @@ -137,13 +180,14 @@ public void onFetchConfigurationCompleted(
}

AuthorizationRequest authRequest = authRequestBuilder.build();

AuthorizationService authService = new AuthorizationService(context);
AuthorizationService authService = new AuthorizationService(context, configuration);
Intent authIntent = authService.getAuthorizationRequestIntent(authRequest);
currentActivity.startActivityForResult(authIntent, 0);

}
});
},
builder
);

}

Expand All @@ -155,14 +199,20 @@ 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);
final AppAuthConfiguration configuration = createAppAuthConfiguration(builder);

AuthorizationServiceConfiguration.fetchFromIssuer(
Uri.parse(issuer),
// store setting in private field for later use in onActivityResult handler
this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;

AuthorizationServiceConfiguration.fetchFromUrl(
buildConfigurationUriFromIssuer(issuerUri),
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
public void onFetchConfigurationCompleted(
@Nullable AuthorizationServiceConfiguration serviceConfiguration,
Expand All @@ -187,9 +237,7 @@ public void onFetchConfigurationCompleted(

TokenRequest tokenRequest = tokenRequestBuilder.build();


AuthorizationService authService = new AuthorizationService(context);

AuthorizationService authService = new AuthorizationService(context, configuration);
authService.performTokenRequest(tokenRequest, new AuthorizationService.TokenResponseCallback() {
@Override
public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable AuthorizationException ex) {
Expand All @@ -203,7 +251,8 @@ public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable
});

}
});
},
builder);
}

@Override
Expand All @@ -217,9 +266,11 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
}

final Promise authorizePromise = this.promise;
final AppAuthConfiguration configuration = createAppAuthConfiguration(
createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests)
);

AuthorizationService authService = new AuthorizationService(this.reactContext);

AuthorizationService authService = new AuthorizationService(this.reactContext, configuration);
authService.performTokenRequest(
response.createTokenExchangeRequest(),
new AuthorizationService.TokenResponseCallback() {
Expand Down Expand Up @@ -248,4 +299,4 @@ public void onNewIntent(Intent intent) {
public String getName() {
return "RNAppAuth";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.reactlibrary.utils;

/*
* 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 UnsafeConnectionBuilder implements ConnectionBuilder {

public static final UnsafeConnectionBuilder INSTANCE = new UnsafeConnectionBuilder();

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 UnsafeConnectionBuilder() {
// 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;
}
}
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface AuthConfiguration extends BaseAuthConfiguration {
scopes: string[];
redirectUrl: string;
additionalParameters?: { [name: string]: string };
dangerouslyAllowInsecureHttpRequests?: boolean;
}

export interface RevokeConfiguration {
Expand Down
Loading

0 comments on commit 2e17c94

Please sign in to comment.