Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

}

ext {
Expand Down Expand Up @@ -99,4 +100,6 @@ dependencies {

implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"

implementation 'com.google.crypto.tink:tink-android:1.3.0-rc1'
}
14 changes: 12 additions & 2 deletions app/src/main/java/org/schabi/newpipe/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;

import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
import com.nostra13.universalimageloader.core.ImageLoader;
Expand All @@ -21,9 +21,10 @@
import org.acra.config.ACRAConfigurationException;
import org.acra.config.ConfigurationBuilder;
import org.acra.sender.ReportSenderFactory;
import org.schabi.newpipe.database.Database;
import org.schabi.newpipe.database.RemoteDatabase;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.utils.Localization;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.report.UserAction;
Expand All @@ -37,6 +38,7 @@
import java.util.Collections;
import java.util.List;

import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.exceptions.CompositeException;
import io.reactivex.exceptions.MissingBackpressureException;
Expand Down Expand Up @@ -107,6 +109,14 @@ public void onCreate() {

// Check for new version
new CheckForNewAppVersionTask().execute();

Database database = NewPipeDatabase.getInstance(this);
if(database instanceof RemoteDatabase){
((RemoteDatabase) database).sync().observeOn(AndroidSchedulers.mainThread()).subscribe(() -> {}, e -> {
Toast.makeText(this, "Failed to sync data from server", Toast.LENGTH_SHORT).show();
});
}

}

protected Downloader getDownloader() {
Expand Down
10 changes: 9 additions & 1 deletion app/src/main/java/org/schabi/newpipe/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import android.widget.ImageView;
import android.widget.TextView;

import org.schabi.newpipe.auth.AuthService;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
Expand Down Expand Up @@ -529,7 +530,14 @@ private void initFragments() {
StateSaver.clearStateFiles();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_LINK_TYPE)) {
handleIntent(getIntent());
} else NavigationHelper.gotoMainFragment(getSupportFragmentManager());
} else {
AuthService authService = AuthService.getInstance(getApplicationContext());
if(!authService.isLoggedIn() && !authService.skipLogin()){
NavigationHelper.openLoginFragment(getSupportFragmentManager());
}else{
NavigationHelper.gotoMainFragment(getSupportFragmentManager());
}
}
}

/*//////////////////////////////////////////////////////////////////////////
Expand Down
52 changes: 42 additions & 10 deletions app/src/main/java/org/schabi/newpipe/NewPipeDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,71 @@
import android.content.Context;
import android.support.annotation.NonNull;

import org.schabi.newpipe.auth.AuthService;
import org.schabi.newpipe.database.AppDatabase;
import org.schabi.newpipe.database.Database;
import org.schabi.newpipe.database.RemoteDatabaseImpl;

import static org.schabi.newpipe.database.AppDatabase.DATABASE_NAME;
import static org.schabi.newpipe.database.Migrations.MIGRATION_11_12;
import static org.schabi.newpipe.database.Migrations.MIGRATION_12_13;

public final class NewPipeDatabase {

private static volatile AppDatabase databaseInstance;
private static volatile Database remoteDatabaseInstance;
private static volatile Database localDatabaseInstance;

private NewPipeDatabase() {
//no instance
}

private static AppDatabase getDatabase(Context context) {
private static Database getRemoteDatabase(AppDatabase roomDb, Context context) {
return new RemoteDatabaseImpl(roomDb, context);
}

private static Database getLocalDatabase(Context context) {
return Room
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_11_12)
.addMigrations(MIGRATION_11_12, MIGRATION_12_13)
.fallbackToDestructiveMigration()
.build();
}

@NonNull
public static AppDatabase getInstance(@NonNull Context context) {
AppDatabase result = databaseInstance;
if (result == null) {
private static Database getRemoteInstance(@NonNull Context context) {
AppDatabase roomDb = (AppDatabase) getLocalInstance(context);
if (remoteDatabaseInstance == null) {
synchronized (NewPipeDatabase.class) {
result = databaseInstance;
if (result == null) {
databaseInstance = (result = getDatabase(context));
if (remoteDatabaseInstance == null) {
remoteDatabaseInstance = getRemoteDatabase(roomDb, context);
}
}
}

return result;
return remoteDatabaseInstance;
}

@NonNull
private static Database getLocalInstance(@NonNull Context context) {
if (localDatabaseInstance == null) {
synchronized (NewPipeDatabase.class) {
if (localDatabaseInstance == null) {
localDatabaseInstance = getLocalDatabase(context);
}
}
}

return localDatabaseInstance;
}

@NonNull
public static Database getInstance(@NonNull Context context) {
boolean loggedIn = AuthService.getInstance(context).isLoggedIn();
if(loggedIn){
return getRemoteInstance(context);
}else {
return getLocalInstance(context);
}
}

}
168 changes: 168 additions & 0 deletions app/src/main/java/org/schabi/newpipe/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package org.schabi.newpipe.auth;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.grack.nanojson.JsonBuilder;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonWriter;

import org.schabi.newpipe.database.RemoteDatabaseClient;
import org.schabi.newpipe.database.RemoteDatabaseException;

import java.security.NoSuchAlgorithmException;
import java.util.Objects;

import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;

public class AuthService implements SharedPreferences.OnSharedPreferenceChangeListener {

private static volatile AuthService instance;

public static final String LOGIN_ENDPOINT = "/auth/signin";
public static final String SIGNUP_ENDPOINT = "/auth/signup";

private final Context context;
private final SharedPreferences sharedPreferences;
private final RemoteDatabaseClient client;

private String username;
private String authToken;
private Boolean loggedIn;

public static AuthService getInstance(@NonNull Context context) {
if (instance == null) {
synchronized (AuthService.class) {
if (instance == null) {
instance = new AuthService(context);
}
}
}

return instance;
}

private AuthService(Context context) {
this.context = context;
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
this.sharedPreferences.registerOnSharedPreferenceChangeListener(this);
this.client = RemoteDatabaseClient.getInstance(context);
}

public boolean isLoggedIn(){
if(null == loggedIn){
loggedIn = sharedPreferences.getBoolean("logged_in", false);
}
return loggedIn;
}

public boolean skipLogin(){
return sharedPreferences.getBoolean("skip_login", false);
}

public Completable login(String username, String password){

return Completable.fromAction(() -> {
String req = buildRequest(username, password);
String response = client.post(LOGIN_ENDPOINT, req);
JsonObject jsonObject = JsonParser.object().from(response);
String tokenType = jsonObject.getString("tokenType");
String accessToken = jsonObject.getString("accessToken");
if(null == tokenType || null == accessToken) throw new RemoteDatabaseException("Unable to authenticate");

// create encryption key
EncryptionUtils encryptionUtils = EncryptionUtils.getInstance(context);
encryptionUtils.createKey(context, username, password);

// update shared prefs
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("username", username);
editor.putString("token", tokenType + " " + accessToken);
editor.putBoolean("logged_in", true);
editor.apply();
this.username = username;
this.authToken = tokenType + " " + accessToken;
this.loggedIn = true;
}).subscribeOn(Schedulers.io());

}

public Completable logout(){

return Completable.fromAction(() -> {
// delete encryption key
EncryptionUtils.getInstance(context).deleteKey(context);
// update shared prefs
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove("username");
editor.remove("token");
editor.putBoolean("logged_in", false);
editor.apply();
this.username = null;
this.authToken = null;
this.loggedIn = false;
}).subscribeOn(Schedulers.io());

}

public Completable signup(String username, String password){

return Completable.fromAction(() -> {
String req = buildRequest(username, password);
client.post(SIGNUP_ENDPOINT, req);
}).andThen(login(username, password)).subscribeOn(Schedulers.io());

}

@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals("token")){
authToken = sharedPreferences.getString(key, null);
}else if(key.equals("logged_in")){
loggedIn = sharedPreferences.getBoolean(key, false);
}else if(key.equals("username")){
username = sharedPreferences.getString(key, null);
}
}

@Nullable
private String getHash(String input){
String hash = null;
try {
hash = EncryptionUtils.getSHA(input);
} catch (NoSuchAlgorithmException e) {
// ?
}
return hash;
}

private String buildRequest(String username, String password){
String usernameHash = getHash(username);
String passwordHash = getHash(password);
Objects.requireNonNull(usernameHash, "failed to calculate username hash");
Objects.requireNonNull(passwordHash, "failed to calculate password hash");
JsonBuilder json = JsonObject.builder();
json.value("username", usernameHash);
json.value("password", passwordHash);
return JsonWriter.string(json.done());
}

public String getToken(){
if(null == authToken){
authToken = sharedPreferences.getString("token", null);
}
return authToken;
}

public String getUsername(){
if(null == username){
username = sharedPreferences.getString("username", null);
}
return username;
}
}
Loading