fbshipit-source-id: b14273e883aba6de7b817801a1b04e54a29a6366
This commit is contained in:
@@ -121,11 +121,13 @@ android {
|
|||||||
buildConfigField "boolean", "IS_INTERNAL_BUILD", 'true'
|
buildConfigField "boolean", "IS_INTERNAL_BUILD", 'true'
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
|
stl 'c++_shared'
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
arguments '-DANDROID_TOOLCHAIN=clang'
|
arguments '-DANDROID_TOOLCHAIN=clang'
|
||||||
|
arguments '-DANDROID_STL=c++_shared'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +165,7 @@ android {
|
|||||||
implementation deps.okhttp3
|
implementation deps.okhttp3
|
||||||
implementation 'com.facebook.litho:litho-core:0.15.0'
|
implementation 'com.facebook.litho:litho-core:0.15.0'
|
||||||
implementation 'com.facebook.litho:litho-widget:0.15.0'
|
implementation 'com.facebook.litho:litho-widget:0.15.0'
|
||||||
implementation fileTree(dir: 'plugins/console/dependencies', include: ['*.jar'])
|
implementation 'org.mozilla:rhino:1.7.10'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ dependencies {
|
|||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
// ...
|
|
||||||
// Litho
|
// Litho
|
||||||
implementation 'com.facebook.litho:litho-core:0.15.0'
|
implementation 'com.facebook.litho:litho-core:0.15.0'
|
||||||
implementation 'com.facebook.litho:litho-widget:0.15.0'
|
implementation 'com.facebook.litho:litho-widget:0.15.0'
|
||||||
@@ -59,5 +58,4 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
implementation project(':android')
|
implementation project(':android')
|
||||||
//implementation project(':sonar')
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
build.gradle
25
build.gradle
@@ -1,10 +1,3 @@
|
|||||||
/*
|
|
||||||
* This file was generated by the Gradle 'init' task.
|
|
||||||
*
|
|
||||||
* This is a general purpose Gradle build.
|
|
||||||
* Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds/
|
|
||||||
*/
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
@@ -17,9 +10,6 @@ buildscript {
|
|||||||
classpath "com.github.ben-manes:gradle-versions-plugin:${GRADLE_VERSIONS_PLUGIN_VERSION}"
|
classpath "com.github.ben-manes:gradle-versions-plugin:${GRADLE_VERSIONS_PLUGIN_VERSION}"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN_VERSION}"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN_VERSION}"
|
||||||
classpath 'de.undercouch:gradle-download-task:3.1.2'
|
classpath 'de.undercouch:gradle-download-task:3.1.2'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +23,7 @@ subprojects {
|
|||||||
ext {
|
ext {
|
||||||
minSdkVersion = 15
|
minSdkVersion = 15
|
||||||
targetSdkVersion = 25
|
targetSdkVersion = 25
|
||||||
compileSdkVersion = 26
|
compileSdkVersion = 27
|
||||||
buildToolsVersion = '27.0.3'
|
buildToolsVersion = '27.0.3'
|
||||||
sourceCompatibilityVersion = JavaVersion.VERSION_1_7
|
sourceCompatibilityVersion = JavaVersion.VERSION_1_7
|
||||||
targetCompatibilityVersion = JavaVersion.VERSION_1_7
|
targetCompatibilityVersion = JavaVersion.VERSION_1_7
|
||||||
@@ -41,15 +31,15 @@ ext {
|
|||||||
|
|
||||||
ext.deps = [
|
ext.deps = [
|
||||||
// Android support
|
// Android support
|
||||||
supportAnnotations : 'com.android.support:support-annotations:27.0.2',
|
supportAnnotations : 'com.android.support:support-annotations:27.1.1',
|
||||||
supportAppCompat : 'com.android.support:appcompat-v7:26.1.0',
|
supportAppCompat : 'com.android.support:appcompat-v7:27.1.1',
|
||||||
supportCoreUi : 'com.android.support:support-core-ui:26.1.0',
|
supportCoreUi : 'com.android.support:support-core-ui:27.1.1',
|
||||||
supportRecyclerView: 'com.android.support:recyclerview-v7:26.1.0',
|
supportRecyclerView: 'com.android.support:recyclerview-v7:27.1.1',
|
||||||
supportEspresso : 'com.android.support.test.espresso:espresso-core:2.2.2',
|
supportEspresso : 'com.android.support.test.espresso:espresso-core:2.2.2',
|
||||||
supportEspressoIntents : 'com.android.support.test.espresso:espresso-intents:2.2.2',
|
supportEspressoIntents : 'com.android.support.test.espresso:espresso-intents:2.2.2',
|
||||||
supportTestRunner : 'com.android.support.test:runner:1.0.1',
|
supportTestRunner : 'com.android.support.test:runner:1.0.2',
|
||||||
// Arch
|
// Arch
|
||||||
archPaging : 'android.arch.paging:runtime:1.0.0-alpha3',
|
archPaging : 'android.arch.paging:runtime:1.0.0',
|
||||||
// First-party
|
// First-party
|
||||||
soloader : 'com.facebook.soloader:soloader:0.4.1',
|
soloader : 'com.facebook.soloader:soloader:0.4.1',
|
||||||
screenshot : 'com.facebook.testing.screenshot:core:0.5.0',
|
screenshot : 'com.facebook.testing.screenshot:core:0.5.0',
|
||||||
@@ -61,7 +51,6 @@ ext.deps = [
|
|||||||
guava : 'com.google.guava:guava:20.0',
|
guava : 'com.google.guava:guava:20.0',
|
||||||
robolectric : 'org.robolectric:robolectric:3.0',
|
robolectric : 'org.robolectric:robolectric:3.0',
|
||||||
junit : 'junit:junit:4.12',
|
junit : 'junit:junit:4.12',
|
||||||
guava : 'com.google.guava:guava:20.0',
|
|
||||||
stetho : 'com.facebook.stetho:stetho:1.5.0',
|
stetho : 'com.facebook.stetho:stetho:1.5.0',
|
||||||
okhttp3 : 'com.squareup.okhttp3:okhttp:3.10.0'
|
okhttp3 : 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ and install the dependencies by running `pod install`. When you open the Xcode w
|
|||||||
|
|
||||||
* We haven't released the dependency to CocoaPods, because we weren't able to successfully validate the podspec of SonarKit. You could help us out by fixing this [issue](https://github.com/facebook/Sonar/issues/11) by submitting a PR to the repo.
|
* We haven't released the dependency to CocoaPods, because we weren't able to successfully validate the podspec of SonarKit. You could help us out by fixing this [issue](https://github.com/facebook/Sonar/issues/11) by submitting a PR to the repo.
|
||||||
* If you do not use CocoaPods as a dependency management tool then currently there is no way to integrate SonarKit other than manually including all the dependencies and building it.
|
* If you do not use CocoaPods as a dependency management tool then currently there is no way to integrate SonarKit other than manually including all the dependencies and building it.
|
||||||
|
* For Android, Sonar works with both emulators and physical devices connected through USB. However on iOS, we don't yet support physical devices.
|
||||||
* Also Sonar doesn't work with swift projects as its written in C++ and had C++ dependencies. But we are working on supporting sonar for swift projects. You can find this issue [here](https://github.com/facebook/Sonar/issues/13)
|
* Also Sonar doesn't work with swift projects as its written in C++ and had C++ dependencies. But we are working on supporting sonar for swift projects. You can find this issue [here](https://github.com/facebook/Sonar/issues/13)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
- (void)send:(NSString *)method withParams:(NSDictionary *)params
|
- (void)send:(NSString *)method withParams:(NSDictionary *)params
|
||||||
{
|
{
|
||||||
conn_->send([method UTF8String], facebook::cxxutils::convertIdToFollyDynamic(params));
|
conn_->send([method UTF8String], facebook::cxxutils::convertIdToFollyDynamic(params, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver
|
- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"7zip-bin-mac": "^1.0.1",
|
|
||||||
"babel-eslint": "^8.2.1",
|
"babel-eslint": "^8.2.1",
|
||||||
"electron": "^2.0.1",
|
"electron": "^2.0.1",
|
||||||
"electron-builder": "^19.49.0",
|
"electron-builder": "^19.49.0",
|
||||||
@@ -83,5 +82,8 @@
|
|||||||
"build": "yarn rm-dist && NODE_ENV=production node scripts/build-release.js $@",
|
"build": "yarn rm-dist && NODE_ENV=production node scripts/build-release.js $@",
|
||||||
"fix": "eslint . --fix",
|
"fix": "eslint . --fix",
|
||||||
"lint": "eslint . && flow check"
|
"lint": "eslint . && flow check"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"7zip-bin-mac": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,68 @@
|
|||||||
{
|
{
|
||||||
"command": "SandcastleUniversalCommand",
|
"command": "SandcastleUniversalCommand",
|
||||||
"args": {
|
"args": {
|
||||||
"name": "Release public Sonar build",
|
"name": "Release public Sonar build",
|
||||||
"oncall": "danielbuechele",
|
"oncall": "danielbuechele",
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"name": "sonar_release_public_build",
|
"name": "Clone from GitHub",
|
||||||
"required": true,
|
"required": true,
|
||||||
"shell": "cd ../xplat/sonar/scripts && ./public-build.sh"
|
"shell": "git -c http.proxy=fwdproxy:8080 -c https.proxy=fwdproxy:8080 clone https://github.com/facebook/Sonar.git ../xplat/sonar-public"
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
"name": "Create Version",
|
||||||
|
"required": true,
|
||||||
|
"shell": "cat ../xplat/sonar-public/package.json | jq -r '.version' | sed -E 's/[0-9]+$/__VERSION__/g' > $SANDCASTLE_NEXUS/VERSION"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Copy Sandcastle script",
|
||||||
|
"required": true,
|
||||||
|
"shell": "cp ../xplat/sonar/scripts/sandcastle-build.sh ../xplat/sonar-public/scripts/sandcastle-build.sh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create build",
|
||||||
|
"required": true,
|
||||||
|
"shell": "cd ../xplat/sonar-public/scripts && ./sandcastle-build.sh $(cat $SANDCASTLE_NEXUS/VERSION)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Copy artifacts for syncing",
|
||||||
|
"required": true,
|
||||||
|
"shell": "cp -R ../xplat/sonar-public/dist/Sonar-mac.zip $SANDCASTLE_NEXUS/Sonar-mac.zip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sync to local",
|
||||||
|
"step_type": "sync_step",
|
||||||
|
"from": "remote",
|
||||||
|
"to": "local",
|
||||||
|
"paths": [
|
||||||
|
"Sonar-mac.zip",
|
||||||
|
"VERSION"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Publish to github",
|
||||||
|
"required": true,
|
||||||
|
"shell": "curl -o RELEASE.json -x fwdproxy:8080 --silent --data '{ \"tag_name\": \"v'$(cat $SANDCASTLE_NEXUS/VERSION)'\", \"target_commitish\": \"master\", \"name\": \"v'$(cat $SANDCASTLE_NEXUS/VERSION)'\", \"body\": \"\", \"draft\": false, \"prerelease\": false}' https://api.github.com/repos/facebook/Sonar/releases?access_token=$(secrets_tool get SONAR_GITHUB_TOKEN)",
|
||||||
|
"shell_type": "SandcastleLocalShell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Upload",
|
||||||
|
"required": true,
|
||||||
|
"shell": "curl -x fwdproxy:8080 $(cat RELEASE.json | jq -r '.upload_url' | sed -e 's#{?name,label}##')'?access_token='$(secrets_tool get SONAR_GITHUB_TOKEN)'&name=Sonar.zip' --header 'Content-Type: application/zip' --upload-file $SANDCASTLE_NEXUS'/Sonar-mac.zip' -X POST",
|
||||||
|
"shell_type": "SandcastleLocalShell"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"alias": "sonar_release_public_build",
|
"alias": "sonar_release_public_build",
|
||||||
"capabilities": {
|
"capabilities": {
|
||||||
"vcs": "fbcode-fbsource",
|
"vcs": "fbcode-fbsource",
|
||||||
"type": "lego"
|
"type": "lego-mac"
|
||||||
},
|
},
|
||||||
"hash": "master"
|
"hash": "master",
|
||||||
|
"report": [
|
||||||
|
{
|
||||||
|
"type": "chirp",
|
||||||
|
"users": ["__USER__"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
TOKEN=$(secrets_tool get SONAR_GITHUB_TOKEN)
|
|
||||||
GITHUB_ORG="facebook"
|
|
||||||
GITHUB_REPO="Sonar"
|
|
||||||
|
|
||||||
cd ../../ || exit
|
echo "✨ Creating new Sonar release on GitHub..."
|
||||||
|
MAJOR=$(curl -x fwdproxy:8080 --silent https://raw.githubusercontent.com/facebook/Sonar/master/package.json | jq -r '.version' | sed -E 's/[0-9]+$//g')
|
||||||
|
|
||||||
function jsonValue() {
|
echo "What should the patch version of the next release be? (v${MAJOR}_)"
|
||||||
python -c 'import json,sys;obj=json.load(sys.stdin);print obj["'$1'"]' || echo ''
|
|
||||||
}
|
|
||||||
|
|
||||||
git -c http.proxy=fwdproxy:8080 -c https.proxy=fwdproxy:8080 clone https://github.com/facebook/Sonar.git sonar-public
|
read -r VERSION
|
||||||
cp sonar/scripts/sandcastle-build.sh sonar-public/scripts/sandcastle-build.sh
|
if ! [[ $VERSION =~ ^[0-9]+$ ]] ; then
|
||||||
# third-party dependencies are not on github, so we need to copy them in place
|
echo "error: Version needs to be a number" >&2; exit 1
|
||||||
cp -r sonar/third-party sonar-public/third-party
|
|
||||||
cd sonar-public/scripts && ./sandcastle-build.sh "$(git rev-list HEAD --count || echo 0)"
|
|
||||||
|
|
||||||
VERSION=$(plutil -p ./sonar-public/dist/mac/Sonar.app/Contents/Info.plist | awk '/CFBundleShortVersionString/ {print substr($3, 2, length($3)-2)}')
|
|
||||||
|
|
||||||
RELEASE_JSON=$(curl -x fwdproxy:8080 --silent --data '{
|
|
||||||
"tag_name": "v'$VERSION'",
|
|
||||||
"target_commitish": "master",
|
|
||||||
"name": "v'$VERSION'",
|
|
||||||
"body": "",
|
|
||||||
"draft": false,
|
|
||||||
"prerelease": false
|
|
||||||
}' https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/releases?access_token=$TOKEN)
|
|
||||||
|
|
||||||
RELEASE_ID=$(echo $RELEASE_JSON | jsonValue id)
|
|
||||||
|
|
||||||
if [ -z "${RELEASE_ID}" ]; then
|
|
||||||
echo $RELEASE_JSON
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Created GitHub release ID: $RELEASE_ID"
|
echo "Creating version $MAJOR$VERSION and releasing to GitHub..."
|
||||||
UPLOAD_URL=$(echo $RELEASE_JSON | jsonValue upload_url| sed -e 's#{?name,label}##')
|
TMP_DIR=$(mktemp -d)
|
||||||
ASSET_JSON=$(curl -x fwdproxy:8080 --silent $UPLOAD_URL'?access_token='$TOKEN'&name=Sonar.zip' --header 'Content-Type: application/zip' --upload-file ./sonar-public/dist/Sonar.zip -X POST)
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
JSON=$(cat "$DIR/public-build.json")
|
||||||
|
JSON=${JSON/__VERSION__/$VERSION}
|
||||||
|
JSON=${JSON/__USER__/$USER}
|
||||||
|
echo "$JSON" > "$TMP_DIR/job.json"
|
||||||
|
|
||||||
DOWNLOAD_URL=$(echo $ASSET_JSON | jsonValue browser_download_url)
|
scutil create "$TMP_DIR/job.json"
|
||||||
|
echo "✅ GitHub release will be automatically created once the Sandcastle job finishes."
|
||||||
|
|
||||||
if [ -z "${DOWNLOAD_URL}" ]; then
|
rm -rf "$TMP_DIR"
|
||||||
echo $ASSET_JSON
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Released Sonar v$VERSION"
|
|
||||||
echo "Download: $DOWNLOAD_URL"
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {SonarPlugin} from './plugin.js';
|
|||||||
import plugins from './plugins/index.js';
|
import plugins from './plugins/index.js';
|
||||||
import CertificateProvider from './utils/CertificateProvider';
|
import CertificateProvider from './utils/CertificateProvider';
|
||||||
import type {SecureServerConfig} from './utils/CertificateProvider';
|
import type {SecureServerConfig} from './utils/CertificateProvider';
|
||||||
|
import type Logger from './fb-stubs/Logger';
|
||||||
|
|
||||||
import {RSocketServer, ReactiveSocket, PartialResponder} from 'rsocket-core';
|
import {RSocketServer, ReactiveSocket, PartialResponder} from 'rsocket-core';
|
||||||
import RSocketTCPServer from 'rsocket-tcp-server';
|
import RSocketTCPServer from 'rsocket-tcp-server';
|
||||||
@@ -315,6 +316,7 @@ export class Server extends EventEmitter {
|
|||||||
secureServer: RSocketServer;
|
secureServer: RSocketServer;
|
||||||
insecureServer: RSocketServer;
|
insecureServer: RSocketServer;
|
||||||
certificateProvider: CertificateProvider;
|
certificateProvider: CertificateProvider;
|
||||||
|
connectionTracker: ConnectionTracker;
|
||||||
app: App;
|
app: App;
|
||||||
|
|
||||||
constructor(app: App) {
|
constructor(app: App) {
|
||||||
@@ -322,6 +324,7 @@ export class Server extends EventEmitter {
|
|||||||
this.app = app;
|
this.app = app;
|
||||||
this.connections = new Map();
|
this.connections = new Map();
|
||||||
this.certificateProvider = new CertificateProvider(this, app.logger);
|
this.certificateProvider = new CertificateProvider(this, app.logger);
|
||||||
|
this.connectionTracker = new ConnectionTracker(app.logger);
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +392,10 @@ export class Server extends EventEmitter {
|
|||||||
_trustedRequestHandler = (conn: RSocket, connectRequest: {data: string}) => {
|
_trustedRequestHandler = (conn: RSocket, connectRequest: {data: string}) => {
|
||||||
const server = this;
|
const server = this;
|
||||||
|
|
||||||
const client = this.addConnection(conn, connectRequest.data);
|
const clientData: ClientQuery = JSON.parse(connectRequest.data);
|
||||||
|
this.connectionTracker.logConnectionAttempt(clientData);
|
||||||
|
|
||||||
|
const client = this.addConnection(conn, clientData);
|
||||||
|
|
||||||
conn.connectionStatus().subscribe({
|
conn.connectionStatus().subscribe({
|
||||||
onNext(payload) {
|
onNext(payload) {
|
||||||
@@ -413,7 +419,8 @@ export class Server extends EventEmitter {
|
|||||||
conn: RSocket,
|
conn: RSocket,
|
||||||
connectRequest: {data: string},
|
connectRequest: {data: string},
|
||||||
) => {
|
) => {
|
||||||
const connectionParameters = JSON.parse(connectRequest.data);
|
const clientData = JSON.parse(connectRequest.data);
|
||||||
|
this.connectionTracker.logConnectionAttempt(clientData);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fireAndForget: (payload: {data: string}) => {
|
fireAndForget: (payload: {data: string}) => {
|
||||||
@@ -442,7 +449,7 @@ export class Server extends EventEmitter {
|
|||||||
const {csr, destination} = json;
|
const {csr, destination} = json;
|
||||||
this.certificateProvider.processCertificateSigningRequest(
|
this.certificateProvider.processCertificateSigningRequest(
|
||||||
csr,
|
csr,
|
||||||
connectionParameters.os,
|
clientData.os,
|
||||||
destination,
|
destination,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -459,13 +466,12 @@ export class Server extends EventEmitter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addConnection(conn: ReactiveSocket, queryString: string): Client {
|
addConnection(conn: ReactiveSocket, query: ClientQuery): Client {
|
||||||
const query = JSON.parse(queryString);
|
|
||||||
invariant(query, 'expected query');
|
invariant(query, 'expected query');
|
||||||
|
|
||||||
this.app.logger.warn(`Device connected: ${queryString}`, 'connection');
|
|
||||||
|
|
||||||
const id = `${query.app}-${query.os}-${query.device}`;
|
const id = `${query.app}-${query.os}-${query.device}`;
|
||||||
|
this.app.logger.warn(`Device connected: ${id}`, 'connection');
|
||||||
|
|
||||||
const client = new Client(this.app, id, query, conn);
|
const client = new Client(this.app, id, query, conn);
|
||||||
|
|
||||||
const info = {
|
const info = {
|
||||||
@@ -518,3 +524,34 @@ export class Server extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ConnectionTracker {
|
||||||
|
timeWindowMillis = 20 * 1000;
|
||||||
|
connectionProblemThreshold = 4;
|
||||||
|
|
||||||
|
// "${device}.${app}" -> [timestamp1, timestamp2...]
|
||||||
|
connectionAttempts: Map<string, Array<number>> = new Map();
|
||||||
|
logger: Logger;
|
||||||
|
|
||||||
|
constructor(logger: Logger) {
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
logConnectionAttempt(client: ClientQuery) {
|
||||||
|
const key = `${client.os}-${client.device}-${client.app}`;
|
||||||
|
const time = Date.now();
|
||||||
|
var entry = this.connectionAttempts.get(key) || [];
|
||||||
|
entry.push(time);
|
||||||
|
entry = entry.filter(t => t >= time - this.timeWindowMillis);
|
||||||
|
|
||||||
|
this.connectionAttempts.set(key, entry);
|
||||||
|
if (entry.length >= this.connectionProblemThreshold) {
|
||||||
|
this.logger.error(
|
||||||
|
`Connection loop detected with ${key}. Connected ${
|
||||||
|
entry.length
|
||||||
|
} times in ${(time - entry[0]) / 1000}s.`,
|
||||||
|
'ConnectionTracker',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const minCertExpiryWindowSeconds = 24 * 60 * 60;
|
|||||||
const appNotDebuggableRegex = /debuggable/;
|
const appNotDebuggableRegex = /debuggable/;
|
||||||
const allowedAppNameRegex = /^[a-zA-Z0-9.\-]+$/;
|
const allowedAppNameRegex = /^[a-zA-Z0-9.\-]+$/;
|
||||||
const allowedAppDirectoryRegex = /^\/[ a-zA-Z0-9.\-\/]+$/;
|
const allowedAppDirectoryRegex = /^\/[ a-zA-Z0-9.\-\/]+$/;
|
||||||
|
const logTag = 'CertificateProvider';
|
||||||
|
|
||||||
export type SecureServerConfig = {|
|
export type SecureServerConfig = {|
|
||||||
key: Buffer,
|
key: Buffer,
|
||||||
@@ -124,7 +125,7 @@ export default class CertificateProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateClientCertificate(csr: string): Promise<string> {
|
generateClientCertificate(csr: string): Promise<string> {
|
||||||
this.logger.warn('Creating new client cert', 'CertificateProvider');
|
this.logger.warn('Creating new client cert', logTag);
|
||||||
const csrFile = this.writeToTempFile(csr);
|
const csrFile = this.writeToTempFile(csr);
|
||||||
// Create a certificate for the client, using the details in the CSR.
|
// Create a certificate for the client, using the details in the CSR.
|
||||||
return openssl('x509', {
|
return openssl('x509', {
|
||||||
@@ -145,40 +146,66 @@ export default class CertificateProvider {
|
|||||||
contents: string,
|
contents: string,
|
||||||
csr: string,
|
csr: string,
|
||||||
os: string,
|
os: string,
|
||||||
) {
|
): Promise<void> {
|
||||||
if (os === 'Android') {
|
if (os === 'Android') {
|
||||||
this.extractAppNameFromCSR(csr).then(app => {
|
const appNamePromise = this.extractAppNameFromCSR(csr);
|
||||||
const client = adb.createClient();
|
const deviceIdPromise = appNamePromise.then(app =>
|
||||||
client.listDevices().then((devices: Array<{id: string}>) => {
|
this.getTargetDeviceId(app, destination, csr),
|
||||||
devices.forEach(d =>
|
);
|
||||||
// To find out which device requested the cert, search them
|
return Promise.all([deviceIdPromise, appNamePromise]).then(
|
||||||
// all for a matching csr file.
|
([deviceId, appName]) =>
|
||||||
// It's not important to keep these secret from other apps.
|
this.pushFileToAndroidDevice(
|
||||||
// Just need to make sure each app can find it's own one.
|
deviceId,
|
||||||
this.androidDeviceHasMatchingCSR(destination, d.id, app, csr)
|
appName,
|
||||||
.catch(e =>
|
destination + filename,
|
||||||
this.logger.error(
|
contents,
|
||||||
`Unable to check for matching CSR in ${d.id}:${app}`,
|
),
|
||||||
'CertificateProvider',
|
);
|
||||||
),
|
|
||||||
)
|
|
||||||
.then(isMatch => {
|
|
||||||
if (isMatch) {
|
|
||||||
this.pushFileToAndroidDevice(
|
|
||||||
d.id,
|
|
||||||
app,
|
|
||||||
destination + filename,
|
|
||||||
contents,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (os === 'iOS') {
|
if (os === 'iOS') {
|
||||||
fs.writeFileSync(destination + filename, contents);
|
fs.writeFileSync(destination + filename, contents);
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
return Promise.reject(new Error(`Unsupported device os: ${os}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetDeviceId(
|
||||||
|
appName: string,
|
||||||
|
deviceCsrFilePath: string,
|
||||||
|
csr: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const client = adb.createClient();
|
||||||
|
return client.listDevices().then((devices: Array<{id: string}>) => {
|
||||||
|
const deviceMatchList = devices.map(device =>
|
||||||
|
// To find out which device requested the cert, search them
|
||||||
|
// all for a matching csr file.
|
||||||
|
// It's not important to keep these secret from other apps.
|
||||||
|
// Just need to make sure each app can find it's own one.
|
||||||
|
this.androidDeviceHasMatchingCSR(
|
||||||
|
deviceCsrFilePath,
|
||||||
|
device.id,
|
||||||
|
appName,
|
||||||
|
csr,
|
||||||
|
)
|
||||||
|
.then(isMatch => {
|
||||||
|
return {id: device.id, isMatch};
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.logger.error(
|
||||||
|
`Unable to check for matching CSR in ${device.id}:${appName}`,
|
||||||
|
logTag,
|
||||||
|
);
|
||||||
|
return {id: device.id, isMatch: false};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return Promise.all(deviceMatchList).then(devices => {
|
||||||
|
const matchingIds = devices.filter(m => m.isMatch).map(m => m.id);
|
||||||
|
if (matchingIds.length == 0) {
|
||||||
|
throw new Error(`No matching device found for app: ${appName}`);
|
||||||
|
}
|
||||||
|
return matchingIds[0];
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
androidDeviceHasMatchingCSR(
|
androidDeviceHasMatchingCSR(
|
||||||
@@ -191,14 +218,19 @@ export default class CertificateProvider {
|
|||||||
deviceId,
|
deviceId,
|
||||||
processName,
|
processName,
|
||||||
`cat ${directory + csrFileName}`,
|
`cat ${directory + csrFileName}`,
|
||||||
).then(deviceCsr => {
|
)
|
||||||
return (
|
.then(deviceCsr => {
|
||||||
deviceCsr
|
return (
|
||||||
.toString()
|
deviceCsr
|
||||||
.replace(/\r/g, '')
|
.toString()
|
||||||
.trim() === csr.replace(/\r/g, '').trim()
|
.replace(/\r/g, '')
|
||||||
);
|
.trim() === csr.replace(/\r/g, '').trim()
|
||||||
});
|
);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.logger.error(err, logTag);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pushFileToAndroidDevice(
|
pushFileToAndroidDevice(
|
||||||
@@ -207,10 +239,7 @@ export default class CertificateProvider {
|
|||||||
filename: string,
|
filename: string,
|
||||||
contents: string,
|
contents: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.logger.warn(
|
this.logger.warn(`Deploying ${filename} to ${deviceId}:${app}`, logTag);
|
||||||
`Deploying sonar certificate to ${deviceId}:${app}`,
|
|
||||||
'CertificateProvider',
|
|
||||||
);
|
|
||||||
return this.executeCommandOnAndroid(
|
return this.executeCommandOnAndroid(
|
||||||
deviceId,
|
deviceId,
|
||||||
app,
|
app,
|
||||||
@@ -245,6 +274,13 @@ export default class CertificateProvider {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.logger.error(
|
||||||
|
`Error executing command on android device ${deviceId}:${user}. Command: ${command}`,
|
||||||
|
logTag,
|
||||||
|
);
|
||||||
|
this.logger.error(err, logTag);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,10 +342,7 @@ export default class CertificateProvider {
|
|||||||
})
|
})
|
||||||
.then(output => undefined)
|
.then(output => undefined)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this.logger.warn(
|
this.logger.warn(`Certificate will expire soon: ${filename}`, logTag);
|
||||||
`Certificate will expire soon: ${filename}`,
|
|
||||||
'CertificateProvider',
|
|
||||||
);
|
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -331,7 +364,7 @@ export default class CertificateProvider {
|
|||||||
if (!fs.existsSync(getFilePath(''))) {
|
if (!fs.existsSync(getFilePath(''))) {
|
||||||
fs.mkdirSync(getFilePath(''));
|
fs.mkdirSync(getFilePath(''));
|
||||||
}
|
}
|
||||||
this.logger.info('Generating new CA', 'CertificateProvider');
|
this.logger.info('Generating new CA', logTag);
|
||||||
return openssl('genrsa', {out: caKey, '2048': false})
|
return openssl('genrsa', {out: caKey, '2048': false})
|
||||||
.then(_ =>
|
.then(_ =>
|
||||||
openssl('req', {
|
openssl('req', {
|
||||||
@@ -364,7 +397,7 @@ export default class CertificateProvider {
|
|||||||
generateServerCertificate(): Promise<void> {
|
generateServerCertificate(): Promise<void> {
|
||||||
return this.ensureCertificateAuthorityExists()
|
return this.ensureCertificateAuthorityExists()
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
this.logger.warn('Creating new server cert', 'CertificateProvider');
|
this.logger.warn('Creating new server cert', logTag);
|
||||||
})
|
})
|
||||||
.then(_ => openssl('genrsa', {out: serverKey, '2048': false}))
|
.then(_ => openssl('genrsa', {out: serverKey, '2048': false}))
|
||||||
.then(_ =>
|
.then(_ =>
|
||||||
|
|||||||
0
src/utils/promise.js
Normal file
0
src/utils/promise.js
Normal file
@@ -127,7 +127,8 @@ void SonarWebSocketImpl::doCertificateExchange() {
|
|||||||
folly::SocketAddress address;
|
folly::SocketAddress address;
|
||||||
|
|
||||||
parameters.payload = rsocket::Payload(
|
parameters.payload = rsocket::Payload(
|
||||||
folly::toJson(folly::dynamic::object("os", deviceData_.os)));
|
folly::toJson(folly::dynamic::object("os", deviceData_.os)(
|
||||||
|
"device", deviceData_.device)("app", deviceData_.app)));
|
||||||
address.setFromHostPort(deviceData_.host, insecurePort);
|
address.setFromHostPort(deviceData_.host, insecurePort);
|
||||||
|
|
||||||
connectionIsTrusted_ = false;
|
connectionIsTrusted_ = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user