Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
e6899ed134 | |||
2b634dcf75 | |||
789709695b | |||
f2eb043e7f | |||
e17daa9e41 | |||
dadaaf2a04 | |||
b2eba70188 |
10
.idea/deploymentTargetSelector.xml
generated
Normal file
10
.idea/deploymentTargetSelector.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
8
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
8
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ExtractMethodRecommender" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="minLength" value="515" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
10
.idea/migrations.xml
generated
Normal file
10
.idea/migrations.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
17
.idea/runConfigurations.xml
generated
Normal file
17
.idea/runConfigurations.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -9,8 +9,8 @@ android {
|
||||
applicationId "nl.privacydragon.bookwyrm"
|
||||
minSdk 23
|
||||
targetSdk 34
|
||||
versionCode 12
|
||||
versionName "1.3.5"
|
||||
versionCode 15
|
||||
versionName "1.3.8"
|
||||
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
@ -18,6 +18,7 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
vcsInfo.include false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
@ -26,6 +27,10 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
namespace 'nl.privacydragon.bookwyrm'
|
||||
dependenciesInfo {
|
||||
includeInApk false
|
||||
includeInBundle false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -38,5 +43,7 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
|
||||
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
|
||||
}
|
Binary file not shown.
BIN
app/release/Bookwyrm-v1.3.8.apk
Normal file
BIN
app/release/Bookwyrm-v1.3.8.apk
Normal file
Binary file not shown.
@ -11,8 +11,8 @@
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 12,
|
||||
"versionName": "1.3.5",
|
||||
"versionCode": 15,
|
||||
"versionName": "1.3.8",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
|
@ -9,7 +9,8 @@
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:dataExtractionRules="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher_wyrm"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_wyrm_round"
|
||||
|
@ -11,7 +11,6 @@ import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.webkit.JavascriptInterface;
|
||||
@ -32,23 +31,6 @@ import androidx.core.content.ContextCompat;
|
||||
import com.journeyapps.barcodescanner.ScanContract;
|
||||
import com.journeyapps.barcodescanner.ScanOptions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
public class HandlerActivity extends AppCompatActivity {
|
||||
|
||||
WebView myWebView;
|
||||
@ -118,12 +100,12 @@ public class HandlerActivity extends AppCompatActivity {
|
||||
String defaultValue = "none";
|
||||
SharedPreferences sharedPref = HandlerActivity.this.getSharedPreferences(getString(R.string.server), Context.MODE_PRIVATE);
|
||||
String server = sharedPref.getString(getString(R.string.server), defaultValue);
|
||||
SharedPreferences sharedPrefName = HandlerActivity.this.getSharedPreferences(getString(R.string.name), Context.MODE_PRIVATE);
|
||||
String name = sharedPrefName.getString(getString(R.string.name), defaultValue);
|
||||
SharedPreferences sharedPrefPass = HandlerActivity.this.getSharedPreferences(getString(R.string.pw), Context.MODE_PRIVATE);
|
||||
String pass = sharedPrefPass.getString(getString(R.string.pw), defaultValue);
|
||||
SharedPreferences sharedPrefMagic = HandlerActivity.this.getSharedPreferences(getString(R.string.q), Context.MODE_PRIVATE);
|
||||
String codeMagic = sharedPrefMagic.getString(getString(R.string.q), defaultValue);
|
||||
//SharedPreferences sharedPrefName = HandlerActivity.this.getSharedPreferences(getString(R.string.name), Context.MODE_PRIVATE);
|
||||
//String name = sharedPrefName.getString(getString(R.string.name), defaultValue);
|
||||
//SharedPreferences sharedPrefPass = HandlerActivity.this.getSharedPreferences(getString(R.string.pw), Context.MODE_PRIVATE);
|
||||
//String pass = sharedPrefPass.getString(getString(R.string.pw), defaultValue);
|
||||
//SharedPreferences sharedPrefMagic = HandlerActivity.this.getSharedPreferences(getString(R.string.q), Context.MODE_PRIVATE);
|
||||
//String codeMagic = sharedPrefMagic.getString(getString(R.string.q), defaultValue);
|
||||
//If there is nothing configured yet, the user should be redirected to the main screen for logging in.
|
||||
if (server == "none") {
|
||||
startActivity(new Intent(HandlerActivity.this, nl.privacydragon.bookwyrm.MainActivity.class));
|
||||
@ -150,61 +132,62 @@ public class HandlerActivity extends AppCompatActivity {
|
||||
}
|
||||
//Then all the decryption stuff has to happen. There are a lot of try-catch stuff, because apparently that seems to be needed.
|
||||
//First get the keystore thing.
|
||||
KeyStore keyStore = null;
|
||||
try {
|
||||
keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
} catch (KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//Then, load it. or something. To make sure that it can be used.
|
||||
try {
|
||||
keyStore.load(null);
|
||||
} catch (CertificateException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//Next, retrieve the key to be used for the decryption.
|
||||
Key DragonLikeKey = null;
|
||||
try {
|
||||
DragonLikeKey = keyStore.getKey("BookWyrm", null);
|
||||
} catch (KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//Do something with getting the/a cipher or something.
|
||||
Cipher c = null;
|
||||
try {
|
||||
c = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchPaddingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//And then initiating the cipher, so it can be used.
|
||||
try {
|
||||
c.init(Cipher.DECRYPT_MODE, DragonLikeKey, new GCMParameterSpec(128, codeMagic.getBytes()));
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//Decrypt the password!
|
||||
byte[] truePass = null;
|
||||
try {
|
||||
truePass = c.doFinal(Base64.decode(pass, Base64.DEFAULT));
|
||||
} catch (BadPaddingException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//Convert the decrypted password back to a string.
|
||||
String passw = new String(truePass, StandardCharsets.UTF_8);
|
||||
// KeyStore keyStore = null;
|
||||
// try {
|
||||
// keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
// } catch (KeyStoreException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// //Then, load it. or something. To make sure that it can be used.
|
||||
// try {
|
||||
// keyStore.load(null);
|
||||
// } catch (CertificateException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (NoSuchAlgorithmException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// //Next, retrieve the key to be used for the decryption.
|
||||
// Key DragonLikeKey = null;
|
||||
// try {
|
||||
// DragonLikeKey = keyStore.getKey("BookWyrm", null);
|
||||
// } catch (KeyStoreException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (NoSuchAlgorithmException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (UnrecoverableKeyException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// //Do something with getting the/a cipher or something.
|
||||
// Cipher c = null;
|
||||
// try {
|
||||
// c = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
// } catch (NoSuchAlgorithmException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (NoSuchPaddingException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// //And then initiating the cipher, so it can be used.
|
||||
// try {
|
||||
// c.init(Cipher.DECRYPT_MODE, DragonLikeKey, new GCMParameterSpec(128, codeMagic.getBytes()));
|
||||
// } catch (InvalidAlgorithmParameterException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (InvalidKeyException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// //Decrypt the password!
|
||||
// byte[] truePass = null;
|
||||
// try {
|
||||
// truePass = c.doFinal(Base64.decode(pass, Base64.DEFAULT));
|
||||
// } catch (BadPaddingException e) {
|
||||
// e.printStackTrace();
|
||||
// } catch (IllegalBlockSizeException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// //Convert the decrypted password back to a string.
|
||||
// String passw = new String(truePass, StandardCharsets.UTF_8);
|
||||
//String wacht = passw.replaceAll("'", "\\\\'");
|
||||
|
||||
//A webviewclient thing is needed for some stuff. To automatically log in, the credentials are put in the form by the javascript that is loaded once the page is fully loaded. Then it is automatically submitted if the current page is the login page.
|
||||
String finalToGoServer = toGoServer;
|
||||
@ -213,10 +196,10 @@ public class HandlerActivity extends AppCompatActivity {
|
||||
LoadIndicator.setVisibility(View.GONE);
|
||||
myWebView.setVisibility(View.VISIBLE);
|
||||
|
||||
view.loadUrl("javascript:(function() { document.getElementById('id_password').value = '" + passw + "'; ;})()");
|
||||
view.loadUrl("javascript:(function() { document.getElementById('id_localname').value = '" + name + "'; ;})()");
|
||||
view.loadUrl("javascript:(function() { if (window.location.href == '" + finalToGoServer + "' && !/(review|generatednote|quotation|comment|book)/i.test(window.location.href)) { document.getElementsByName(\"login\")[0].submit();} ;})()");
|
||||
view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "') { document.getElementsByName(\"login\")[0].submit();} ;})()");
|
||||
//view.loadUrl("javascript:(function() { document.getElementById('id_password').value = '" + wacht + "'; ;})()");
|
||||
//view.loadUrl("javascript:(function() { document.getElementById('id_localname').value = '" + name + "'; ;})()");
|
||||
//view.loadUrl("javascript:(function() { if (window.location.href == '" + finalToGoServer + "' && !/(review|generatednote|quotation|comment|book)/i.test(window.location.href)) { document.getElementsByName(\"login\")[0].submit();} ;})()");
|
||||
//view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "') { document.getElementsByName(\"login\")[0].submit();} ;})()");
|
||||
view.loadUrl("javascript:(function() { if (/(review|generatednote|quotation|comment|book)/i.test(window.location.href)) {" +
|
||||
"blocks = document.getElementsByClassName('block');" +
|
||||
"for (let element of blocks){" +
|
||||
@ -311,7 +294,7 @@ public class HandlerActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
// Check if the key event was the Back button and if there's history
|
||||
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
|
||||
myWebView.goBack();
|
||||
@ -319,7 +302,7 @@ public class HandlerActivity extends AppCompatActivity {
|
||||
}
|
||||
// If it wasn't the Back key or there's no web page history, bubble up to the default
|
||||
// system behavior (probably exit the activity)
|
||||
return super.onKeyDown(keyCode, event);
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
@Override
|
||||
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
|
@ -6,7 +6,6 @@ import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
//import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.view.View;
|
||||
@ -71,7 +70,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
//All fields are required, so if one of them is empty, the user should see a warning.
|
||||
if (server.isEmpty() || pass.isEmpty() || name.isEmpty()) {
|
||||
TextView ErrorMessage = (TextView) findViewById(R.id.textView5);
|
||||
ErrorMessage.setTextColor(Color.RED);
|
||||
ErrorMessage.setTextColor(Color.YELLOW);
|
||||
ErrorMessage.setText("ERROR: All fields are required!");
|
||||
} else {
|
||||
//Likely this will be the first time the program is run. So create a new key thing in the android key store happening.
|
||||
|
@ -14,6 +14,7 @@ import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
@ -32,7 +33,11 @@ import androidx.core.content.ContextCompat;
|
||||
import com.journeyapps.barcodescanner.ScanContract;
|
||||
import com.journeyapps.barcodescanner.ScanOptions;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
@ -40,6 +45,7 @@ import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
@ -49,10 +55,20 @@ import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class StartActivity extends AppCompatActivity {
|
||||
WebView myWebView;
|
||||
ProgressBar LoadIndicator;
|
||||
public ValueCallback<Uri[]> omhooglader;
|
||||
String putje = "";
|
||||
String sessie = "";
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -75,6 +91,7 @@ public class StartActivity extends AppCompatActivity {
|
||||
});
|
||||
myWebView = (WebView) findViewById(R.id.webview);
|
||||
myWebView.setVisibility(View.GONE);
|
||||
myWebView.getSettings().setUserAgentString(getString(R.string.gebruikersagent));
|
||||
myWebView.getSettings().setJavaScriptEnabled(true);
|
||||
myWebView.addJavascriptInterface(new Object()
|
||||
{
|
||||
@ -166,6 +183,7 @@ public class StartActivity extends AppCompatActivity {
|
||||
}
|
||||
//Convert the decrypted password back to a string.
|
||||
String passw = new String(truePass, StandardCharsets.UTF_8);
|
||||
//String wacht = passw.replaceAll("'", "\\\\'");
|
||||
|
||||
//A webviewclient thing is needed for some stuff. To automatically log in, the credentials are put in the form by the javascript that is loaded once the page is fully loaded. Then it is automatically submitted if the current page is the login page.
|
||||
myWebView.setWebViewClient(new MyWebViewClient(){
|
||||
@ -173,9 +191,10 @@ public class StartActivity extends AppCompatActivity {
|
||||
LoadIndicator.setVisibility(View.GONE);
|
||||
myWebView.setVisibility(View.VISIBLE);
|
||||
|
||||
view.loadUrl("javascript:(function() { document.getElementById('id_password_confirm').value = '" + passw + "'; ;})()");
|
||||
view.loadUrl("javascript:(function() { document.getElementById('id_localname_confirm').value = '" + name + "'; ;})()");
|
||||
view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "/login') { document.getElementsByName(\"login-confirm\")[0].submit();} ;})()");
|
||||
//view.loadUrl("javascript:(function() { document.getElementById('id_password_confirm').value = '" + wacht + "'; ;})()");
|
||||
//view.loadUrl("javascript:(function() { document.getElementById('id_localname_confirm').value = '" + name + "'; ;})()");
|
||||
//view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "/login') { document.getElementsByName(\"login-confirm\")[0].submit();} ;})()");
|
||||
//view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "/login' && document.title != '403 Forbidden') { this.document.location.href = 'source://' + encodeURI(document.documentElement.outerHTML);} ;})()");
|
||||
view.loadUrl("javascript:(function() { " +
|
||||
"if (document.querySelectorAll(\"[data-modal-open]\")[0]) {" +
|
||||
"let ISBN_Button = document.querySelectorAll(\"[data-modal-open]\")[0];" +
|
||||
@ -197,7 +216,255 @@ public class StartActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
//Here, load the login page of the server. That actually does all that is needed.
|
||||
myWebView.loadUrl("https://" + server + "/login");
|
||||
// try {
|
||||
// getMiddleWareToken(server, name, passw);
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// String geheimeToken = null;
|
||||
// try {
|
||||
// geheimeToken = getMiddleWareToken(server);
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// String gegevens = null;
|
||||
// try {
|
||||
// gegevens = "csrfmiddlewaretoken=" + URLEncoder.encode(geheimeToken, "UTF-8") + "&localname=" + URLEncoder.encode(name, "UTF-8") + "&password=" + URLEncoder.encode(passw, "UTF-8");
|
||||
// } catch (UnsupportedEncodingException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// myWebView.postUrl("https://" + server + "/login", gegevens.getBytes());
|
||||
//myWebView.loadUrl("https://" + server + "/login");
|
||||
// myWebView.setVisibility(View.GONE);
|
||||
// LoadIndicator.setVisibility(View.VISIBLE);
|
||||
// android.webkit.CookieManager oven = android.webkit.CookieManager.getInstance();
|
||||
//myWebView.loadUrl("javascript:this.document.location.href = 'source://' + encodeURI(document.documentElement.outerHTML);");
|
||||
//try {
|
||||
//See if we are already logged in.
|
||||
CookieManager oven = CookieManager.getInstance();
|
||||
String koek = oven.getCookie("https://" + server);
|
||||
if (koek != null) {
|
||||
if (koek.indexOf("sessionid") != -1) {
|
||||
myWebView.loadUrl("https://" + server);
|
||||
} else {
|
||||
//This should get the login page, retreive the csrf-middlewaretoken, and then log the user in using a POST-request.
|
||||
try {
|
||||
getMiddleWareTokenAndLogIn(server, name, passw);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//This should get the login page, retreive the csrf-middlewaretoken, and then log the user in using a POST-request.
|
||||
try {
|
||||
getMiddleWareTokenAndLogIn(server, name, passw);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
//} catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void getMiddleWareTokenAndLogIn(String server, String name, String passw) throws IOException {
|
||||
//Het idee is dat deze functie de loginpagina van de server laadt en dan de 'csrfmiddlewaretoken' uit het inlogformulier haalt,
|
||||
//Zodat dat dan gebruikt kan worden bij het inloggen.
|
||||
//Becuase network operations cannot be done on the main/ui thread, create a new thread for this complete function. Yay!
|
||||
Thread draadje = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
//Load the login page, and do not forget to take some cookies.
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1);
|
||||
//URL url = new URL("https://" + server + "/");
|
||||
String speculaas = "";
|
||||
String speculaasBeslag = "";
|
||||
//The login page loading is done using OkHttpClient.
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request aanvraag = new Request.Builder()
|
||||
.url("https://" + server + "/")
|
||||
.header("User-Agent", getString(R.string.gebruikersagent))
|
||||
.build();
|
||||
//Get an answer!
|
||||
try (Response antwoord = client.newCall(aanvraag).execute()) {
|
||||
if (!antwoord.isSuccessful()) throw new IOException("Unexpected code " + antwoord);
|
||||
//Search the headers for the 'set-cookie' header so we can eat a cookie!
|
||||
Headers cenna = antwoord.headers();
|
||||
for (int i = 0; i < cenna.size(); i++) {
|
||||
if (cenna.name(i).equals("set-cookie")) {
|
||||
speculaas = cenna.value(i);
|
||||
speculaasBeslag = speculaas.split(";")[0];
|
||||
}
|
||||
}
|
||||
//And then get the HTML body.
|
||||
assert antwoord.body() != null;
|
||||
String zooi = antwoord.body().string();
|
||||
// CookieManager koekManager = new CookieManager();
|
||||
// CookieHandler.setDefault(koekManager);
|
||||
// CookieStore bakker = koekManager.getCookieStore();
|
||||
// HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
// try {
|
||||
// InputStream ina = new BufferedInputStream(urlConnection.getInputStream());
|
||||
// byte[] pagina = null;
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// pagina = ina.readAllBytes();
|
||||
// } else {
|
||||
// //I truly hope that this byte array will always be big enough...
|
||||
// //The Tiramisu+ way is much better...
|
||||
// pagina = new byte[30000];
|
||||
// ina.read(pagina);
|
||||
// }
|
||||
// try {
|
||||
// ina.close();
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// //We should not forget closing the connection we used for hearing what csrf cookie and token we needed.
|
||||
// urlConnection.disconnect();
|
||||
// //And now create a string out of the byte array, so we can retreive the middleware token.
|
||||
// String zooi = new String(pagina);
|
||||
//Very easy to get the token by taking the text that it is preceded by in the raw html as the regex for a split() function!
|
||||
String[] opgebroken = zooi.split("name=\"csrfmiddlewaretoken\" value=\"");
|
||||
//For that gives as second element the token, followed by all the following html code. Then strip that code off, using the immediately following characters as regex.
|
||||
String[] breukjes = opgebroken[1].split("\">");
|
||||
//Of course, the token is then the first element in our array.
|
||||
String token = breukjes[0];
|
||||
//Log.d("botbreuk", token);
|
||||
String gegevens = null;
|
||||
//Initiate some strings to use for the delicious csrf cookie.
|
||||
//String speculaas = "", THT = "";
|
||||
// //How to get the cookies? First get the cookie collection, the cookie box so to say, and then...
|
||||
// List<HttpCookie> koektrommel = bakker.get(URI.create("https://" + server));
|
||||
// Log.d("koek", koektrommel.toString());
|
||||
// //... for every cookie in it check to see if it is the csrftoken named cookie.
|
||||
// for (int i = 0; i < koektrommel.size(); ++i) {
|
||||
// HttpCookie koekje = koektrommel.get(i);
|
||||
// if (Objects.equals(koekje.getName(), "csrftoken")) {
|
||||
// //If it is the csrftoken cookie, get the value of it, and the expiration date of it.
|
||||
// speculaas = koekje.toString();
|
||||
// THT = String.valueOf(koekje.getMaxAge());
|
||||
// //Log.d("domein", koekje.getDomain());
|
||||
// }
|
||||
// }
|
||||
//And then set the data string up for use in the POST request, with the csrf middleware token, the username, and the password.
|
||||
try {
|
||||
gegevens = "csrfmiddlewaretoken=" + URLEncoder.encode(token, "UTF-8") + "&localname=" + URLEncoder.encode(name, "UTF-8") + "&password=" + URLEncoder.encode(passw, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
RequestBody keurslijf = new FormBody.Builder()
|
||||
.add("csrfmiddlewaretoken", token)
|
||||
.add("localname", name)
|
||||
.add("password", passw)
|
||||
.build();
|
||||
|
||||
String finalGegevens = gegevens;
|
||||
//Log.d("gegevens", finalGegevens);
|
||||
//Log.d("beslag", speculaasBeslag);
|
||||
String finalSpeculaas = speculaas;
|
||||
//String finalTHT = THT;
|
||||
logInAndGetHTML(server, keurslijf, speculaasBeslag);
|
||||
//Then we have to run a bit of code on the main (UI) thread. To be able to work with the webview...
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//First we have to get the cookie manager of the webview, so we can hand it the csrf cookie.
|
||||
//Without being fed the correct csrf cookie, the Wyrm will refuse our request. The wyrm is a very picky eater!
|
||||
CookieManager oven = CookieManager.getInstance();
|
||||
//Bake the cookie into the webview.
|
||||
oven.setCookie("https://" + server, finalSpeculaas);
|
||||
//And bake the session cookie as well.
|
||||
oven.setCookie("https://" + server, sessie);
|
||||
//And then finally it is time to send a POST request from the webview to log in.
|
||||
//myWebView.postUrl("https://" + server + "/login?next=/", finalGegevens.getBytes());
|
||||
myWebView.loadDataWithBaseURL("https://" + server, putje, null, null, "https://" + server + "/login");
|
||||
}
|
||||
});
|
||||
|
||||
} finally {
|
||||
// //We should not forget closing the connection we used for hearing what csrf cookie and token we needed.
|
||||
// urlConnection.disconnect();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
//^Here ends all that new Thread() code.
|
||||
//⇓Run all the code in the thread.
|
||||
draadje.start();
|
||||
}
|
||||
public void logInAndGetHTML(String server, RequestBody lichaam, String speculoos) throws IOException {
|
||||
// Thread kabel = new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// try {
|
||||
// //Load the login page, and do not forget to take some cookies.
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1);
|
||||
//Create a client using CookieMonster, so we can retrieve cookies from the redirect.
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(new CookieMonster())
|
||||
.build();
|
||||
//URL url = new URL("https://" + server + "/");
|
||||
//CookieManager koekManager = new CookieManager();
|
||||
//CookieHandler.setDefault(koekManager);
|
||||
//CookieStore bakker = koekManager.getCookieStore();
|
||||
Request verzoek = new Request.Builder()
|
||||
.url("https://" + server + "/login?next=/")
|
||||
.header("User-Agent", getString(R.string.gebruikersagent))
|
||||
.addHeader("origin", "https://" + server)
|
||||
.addHeader("cookie", speculoos)
|
||||
.post(lichaam)
|
||||
.build();
|
||||
try (Response reactie = client.newCall(verzoek).execute()) {
|
||||
if (!reactie.isSuccessful())
|
||||
throw new IOException("Unexpected code " + reactie);
|
||||
assert reactie.body() != null;
|
||||
putje = reactie.body().string();
|
||||
}
|
||||
|
||||
// HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
// urlConnection.setRequestProperty("origin", "https://" + server);
|
||||
// byte[] paarden = gegevens.getBytes();
|
||||
// try {
|
||||
// urlConnection.setDoOutput(true);
|
||||
// urlConnection.setChunkedStreamingMode(0);
|
||||
//
|
||||
// OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
|
||||
// out.write(paarden);
|
||||
// out.flush();
|
||||
//
|
||||
// InputStream in = new BufferedInputStream(urlConnection.getInputStream());
|
||||
// byte[] pagina = null;
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// pagina = in.readAllBytes();
|
||||
// } else {
|
||||
// //I truly hope that this byte array will always be big enough...
|
||||
// //The Tiramisu+ way is much better...
|
||||
// pagina = new byte[30000];
|
||||
// in.read(pagina);
|
||||
// }
|
||||
// try {
|
||||
// in.close();
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// html[0] = new String(pagina);
|
||||
// } finally {
|
||||
// urlConnection.disconnect();
|
||||
// }
|
||||
|
||||
// } catch (Exception e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// kabel.start();
|
||||
//Log.d("lichaam", putje);
|
||||
//return putje;
|
||||
}
|
||||
private final ActivityResultLauncher<ScanOptions> barcodeLanceerder = registerForActivityResult(new ScanContract(),
|
||||
result -> {
|
||||
@ -237,7 +504,7 @@ public class StartActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
// Check if the key event was the Back button and if there's history
|
||||
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
|
||||
myWebView.goBack();
|
||||
@ -245,7 +512,7 @@ public class StartActivity extends AppCompatActivity {
|
||||
}
|
||||
// If it wasn't the Back key or there's no web page history, bubble up to the default
|
||||
// system behavior (probably exit the activity)
|
||||
return super.onKeyDown(keyCode, event);
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
@Override
|
||||
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
@ -255,6 +522,33 @@ public class StartActivity extends AppCompatActivity {
|
||||
}
|
||||
return super.onKeyLongPress(keyCode, event);
|
||||
}
|
||||
final class CookieMonster implements Interceptor {
|
||||
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
//Om ingelogd te blijven moeten we het sessiekoekje aan kunnen bieden.
|
||||
//Die moeten we dan wel eerst uit het koekblik pakken!
|
||||
Request eersteVerzoek = chain.request();
|
||||
//Eerst moeten we controleren of er al een sessiekoekje is. Als dat niet zo is, dan is dit het echte eerste verzoek.
|
||||
if (sessie.isEmpty()) {
|
||||
//In dat geval halen we de reactie op om het koekje te kunnen pakken!
|
||||
Response eersteReactie = chain.proceed(chain.request());
|
||||
Headers hoofden = eersteReactie.headers();
|
||||
for (int i = 0; i < hoofden.size(); i++) {
|
||||
if (hoofden.name(i).equals("set-cookie") && hoofden.value(i).startsWith("session")) {
|
||||
sessie = hoofden.value(i);
|
||||
}
|
||||
}
|
||||
//Nadat we het koekje hebben moet de reactie doorgebriefd worden aan de 'client',
|
||||
//die dan het volgende verzoek zal gaan doen vanwege de 302-redirect bij het inloggen.
|
||||
return eersteReactie;
|
||||
}
|
||||
//Het koekje is er! Hoera!
|
||||
//Het nieuwe verzoek moet wel met het sessiekoekje verzonden worden, anders zijn we alsnog niet ingelogd!
|
||||
Request nieuwVerzoek = eersteVerzoek.newBuilder()
|
||||
.addHeader("cookie", sessie)
|
||||
.build();
|
||||
return chain.proceed(nieuwVerzoek);
|
||||
}
|
||||
}
|
||||
//Here is code to make sure that links of the bookwyrm server are handled within the webview client, instead of having it open in the default browser.
|
||||
//Yes, I used the web for this too.
|
||||
private class MyWebViewClient extends WebViewClient {
|
||||
@ -276,6 +570,8 @@ public class StartActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
LoadIndicator.setVisibility(View.VISIBLE);
|
||||
//CookieManager oven = CookieManager.getInstance();
|
||||
//Log.d("oven", oven.getCookie(url));
|
||||
}
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_marginBottom="262dp"
|
||||
android:hint="bookwyrm.social"
|
||||
android:hint="e.g. bookwyrm.social"
|
||||
android:inputType="text"
|
||||
android:minHeight="48dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@ -162,4 +162,4 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/button" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -4,4 +4,5 @@
|
||||
<string name="name">blup</string>
|
||||
<string name="pw">gloep</string>
|
||||
<string name="q">wheeeee</string>
|
||||
<string name="gebruikersagent">Bookwyrm Android/1.3.8</string>
|
||||
</resources>
|
9
app/src/main/res/xml/backup_rules.xml
Normal file
9
app/src/main/res/xml/backup_rules.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="root" path="."/>
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="root" path="."/>
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
@ -1,7 +1,7 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.7.3' apply false
|
||||
id 'com.android.library' version '8.7.3' apply false
|
||||
id 'com.android.application' version '8.8.0' apply false
|
||||
id 'com.android.library' version '8.8.0' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
5
fastlane/metadata/android/en-US/changelogs/13.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/13.txt
Normal file
@ -0,0 +1,5 @@
|
||||
* Fixed some log-in problems.
|
||||
* Changed colour of warning text to be better visible.
|
||||
* Disabled inclusion of dependencies info and VCS info, fixing build problems for F-Droid.
|
||||
* Long-press now actually works.
|
||||
* Other minor changes
|
1
fastlane/metadata/android/en-US/changelogs/14.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/14.txt
Normal file
@ -0,0 +1 @@
|
||||
* Significant log-in improvements, including better security.
|
5
fastlane/metadata/android/en-US/changelogs/15.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/15.txt
Normal file
@ -0,0 +1,5 @@
|
||||
Fixing what turned out not to work for unreleased version 1.3.7:
|
||||
* Significant log-in improvements, including better security.
|
||||
Further changes:
|
||||
* The app now uses a custom User-Agent string.
|
||||
* Two new dependencies have been added: okhttp3, and conscrypt.
|
Binary file not shown.
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 40 KiB |
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Mon Feb 14 18:09:26 CET 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
Reference in New Issue
Block a user